Load libraries

library(signal)
library(tidyverse)
library(here)
library(lubridate)
library(dtplyr)
library(sf)
library(knitr)
library(mgcv)
library(future)
library(furrr)
library(progressr)
library(pracma)

Set a simple console progress bar

handlers("txtprogressbar") # Simple console progress bar

Supress package messages

suppressPackageStartupMessages({
  library(mgcv)
  library(nlme)
})

Load previously created objects

load(file = "objects/data_RS_S2_bands_indices.Rdata")
load(file = "objects/GAM_data_S2.Rdata")
load(file = "objects/smoothed_data_S2.Rdata")
load(file = "objects/monthly_avg_indices_S2.Rdata")

Load Resurvey db

db_Europa_allobs <- read_csv(
  here("data", "clean", "db_Europa_allobs.csv")) %>%
  select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
         EUNISa_2, EUNISa_2_descr) %>%
  mutate(PlotObservationID = factor(PlotObservationID),
         EUNISa_1 = factor(EUNISa_1), EUNISa_2 = factor(EUNISa_2))

Define printall function

printall <- function(tibble) {
  print(tibble, width = Inf)
  }

Read files with band data

I got these files using the GEE code prepared by Bea.

These files contain all observations in the ReSurvey database from 2016 onward. In order to avoid computation problems in GEE, biogeographical units that contain more than 4500 points have been subdivided in ArcGIS.

# Set the folder path
folder_path <- "C:/Data/MOTIVATE/MOTIVATE_RS_data/S2/Bands/all"

# List CSV files
csv_files <- list.files(folder_path, full.names = TRUE, recursive = TRUE)

# Function to extract biogeo and unit from the filename
extract_info <- function(filename) {
  first_word <- strsplit(filename, "_")[[1]][1]
  biogeo <- str_extract(first_word,
                        "^(ALP|ANA|ARC|ATL|BLACKSEA|BOR|CON|MACARONESIA|MED|PANONIA|STEPPIC)")
  unit <- str_remove(first_word, biogeo)
  if (is.na(unit) || unit == "") unit <- NA_character_
  list(biogeo = biogeo, unit = unit)
  }


# Define column types: force RSrvypl to character, others auto-detected
custom_col_types <- cols(
  RSrvypl = col_character(),
  RSrvyst = col_character(),
  default = col_guess()
)

# Read and process each file
data_list <- lapply(csv_files, function(file) {
  info <- extract_info(basename(file)) # Use only the filename
  
  # Read the file
  df <- read_csv(file, col_types = custom_col_types) %>%
    # Remove columns that give column type problems when combining data
    select(-starts_with("EUNIS"), -starts_with("ReSurvey")) %>%
    mutate(biogeo = info$biogeo, unit = info$unit)
  
  return(df)
  })

# Combine all data
data_RS_S2_bands <- bind_rows(data_list) %>%
  rename(PlotObservationID = PltObID)

# View the resulting tibble
print(data_RS_S2_bands)

# Counts per biogeo and unit
print(data_RS_S2_bands %>% count(biogeo, unit), n = 100)

Some checks

Check that the year in the date of the images is not different to the sampling year:

data_RS_S2_bands %>% dplyr::filter(year != year(date))

Check how many different images are for each observation, date and time:

data_RS_S2_bands %>% group_by(PlotObservationID, date, time_utc) %>%
  summarise(n_images = n_distinct(image_id), .groups = "drop") %>%
  count(n_images)

Average the bands

When there is more than one image for each point and day, average the values of the bands:

Calculate indices

Save:

Plot n_daytime:

data_RS_S2_bands_indices %>%
  group_by(PlotObservationID) %>%
  summarise(n_days = first(n_days)) %>% ungroup() %>%
  ggplot(aes(x = n_days)) + geom_histogram(color = "black", fill = "white") +
  theme_minimal()

Compute phenological metrics from models fitted to time series data

Function

HERE (already done in Landsat): Modify function and rerun everything from here (TBD)

Using GAMs, reweighting and 3 iterations.

Using both a change detection method (maximum slope) and a threshold method (50% amplitude) to calculate sos and eos.

Approach similar to https://doi.org/10.1016/j.jag.2020.102172 for GAM fitting and change detection method, and to https://www.mdpi.com/2072-4292/12/22/3738 fot threshold method.

Define function to compute phenology metrics using GAM fit and NDVI / EVI / SAVI:

compute_metrics_models <- function(df, index_cols = c("NDVI", "EVI", "SAVI")) {
  suppressPackageStartupMessages({
    library(mgcv)
    library(nlme)
    })
  
  plan(multisession)  # Set up parallel processing
  
  # Create a list of index-specific data frames
  index_dfs <- lapply(index_cols, function(index_col) {
    list(index_col = index_col, df = df %>%
           select(DOY, PlotObservationID, all_of(index_col)))
    })
  
  # Define the processing function for each index
  process_index <- function(index_data) {
    index_col <- index_data$index_col
    df_index <- index_data$df %>%
      filter(!is.na(.data[[index_col]])) %>%
      arrange(DOY)
    
    plot_id <- unique(df_index$PlotObservationID)

    if (nrow(df_index) < 10) {
      message("  Skipped: insufficient data (< 10 rows)")
      return(tibble(PlotObservationID = plot_id, index = index_col,
                    sos_slope = NA_real_, sos_threshold = NA_real_,
                    pos = NA_real_, eos_slope = NA_real_, 
                    eos_threshold = NA_real_, auc_slope = NA_real_,
                    auc_threshold = NA_real_, Vmax = NA_real_,
                    DOY = df_index$DOY, value = NA_real_))
    }
    
    # Replace early/late DOY values
    base_value_early <- mean(df_index %>% filter(DOY <= 50) %>% 
                               pull(index_col), na.rm = TRUE)
    base_value_late  <- mean(df_index %>% filter(DOY >= 315) %>% 
                               pull(index_col), na.rm = TRUE)

    df_index <- df_index %>%
      mutate(!!index_col := case_when(
        DOY <= 50 ~ base_value_early,
        DOY >= 315 ~ base_value_late,
        TRUE ~ .data[[index_col]]
      ))

    x <- df_index$DOY
    y <- df_index[[index_col]]
    weights <- rep(1, length(y))
    
    # GAM fit
    pred <- NULL
    for (i in 1:3) {
      gam_fit <- tryCatch({
        mgcv::bam(y ~ s(x, bs = "tp"),weights = weights)
        }, error = function(e) {
          message("  GAM fitting failed for ", plot_id, " - ", index_col, ": ", 
                  e$message)
          return(NULL)
          })
      if (is.null(gam_fit)) {
        return(tibble(
          PlotObservationID = plot_id,
          index = index_col,
          sos_slope = NA_real_,
          sos_threshold = NA_real_,
          pos = NA_real_,
          eos_slope = NA_real_, 
          eos_threshold = NA_real_, 
          auc_slope = NA_real_,
          auc_threshold = NA_real_, 
          Vmin_pre = NA_real_, 
          Vmin_post = NA_real_,
          Vmax = NA_real_, 
          u_sos = NA_real_, 
          u_eos = NA_real_,
          DOY = df_index$DOY,
          value = NA_real_))
        }
      
      pred <- tryCatch({
        predict(gam_fit, newdata = tibble(x = x))
        }, error = function(e) {
          message("Prediction failed for ", plot_id, " - ", index_col, ": ",
                  e$message)
          return(rep(NA_real_, length(x)))
          })
      
      idx_between <- which(x > 50 & x < 315 & !is.na(pred) & pred != 0)
      weights <- rep(1, length(y))
      weights[idx_between] <- (y[idx_between] / (pred[idx_between] + 1e-6))^4
      weights[weights > 1 | is.na(weights)] <- 1
      }
    
    # Compute metrics
    slope <- c(NA, diff(pred))
    idx <- which(x >= 50 & x <= 315)
    pos <- if (length(idx) > 0) x[idx][which.max(pred[idx])] else NA_real_

    sos_slope <- if (!is.na(pos)) {
      idx <- which(x < pos)
      if (length(idx) > 0) x[idx][which.max(slope[idx])] else NA_real_
    } else NA_real_

    eos_slope <- if (!is.na(pos)) {
      idx <- which(x > pos)
      if (length(idx) > 0) x[idx][which.min(slope[idx])] else NA_real_
    } else NA_real_

    integration_idx_slope <- which(x >= sos_slope & x <= 
                                     eos_slope & !is.na(pred))
    auc_slope <- if (length(integration_idx_slope) > 1) {
      sum(diff(x[integration_idx_slope]) * 
            zoo::rollmean(pred[integration_idx_slope], 2))
      } else NA_real_
    
    # Vmin antes y después del pico
    Vmin_pre <- if (!is.na(pos)) min(pred[x <= pos], na.rm = TRUE)else NA_real_
    Vmin_post <- if (!is.na(pos)) min(pred[x >= pos], na.rm = TRUE) else NA_real_
    Vmax <- max(pred, na.rm = TRUE)
    
    # Umbrales relativos
    p <- 0.5
    u_sos <- if (!is.na(Vmin_pre)) Vmin_pre + p * (Vmax - Vmin_pre) else NA_real_
    u_eos <- if (!is.na(Vmin_post)) Vmin_post + p * (Vmax - Vmin_post) else NA_real_
    
    # DOY donde se cruzan los umbrales
    sos_threshold <- if (!is.na(u_sos)) x[which(pred >= u_sos)[1]] else NA_real_
    eos_threshold <- if (!is.na(u_eos)) x[rev(which(pred >= u_eos))[1]] else NA_real_
    
    integration_idx_threshold <- which(x >= sos_threshold & 
                                         x <= eos_threshold & !is.na(pred))
    auc_threshold <- if (length(integration_idx_threshold) > 1) {
      sum(diff(x[integration_idx_threshold]) * 
            zoo::rollmean(pred[integration_idx_threshold], 2))
      } else NA_real_
    
    # 1. Predicciones por DOY
    fits_df <- tibble(
      PlotObservationID = unique(df_index$PlotObservationID),
      DOY = x,
      value = pred,
      index = index_col
      )
    
    # 2. Métricas resumen
    metrics_df <- tibble(
      PlotObservationID = unique(df_index$PlotObservationID),
      index = index_col,
      sos_slope = sos_slope,
      sos_threshold = sos_threshold,
      pos = pos,
      eos_slope = eos_slope,
      eos_threshold = eos_threshold,
      auc_slope = auc_slope,
      auc_threshold = auc_threshold,
      Vmin_pre = Vmin_pre,
      Vmin_post = Vmin_post,
      Vmax = Vmax,
      u_sos = u_sos,
      u_eos = u_eos
      )
    
    # 3. Unir por PlotObservationID, index
    final_df <- left_join(fits_df, metrics_df, 
                          by = c("PlotObservationID", "index"))
  }
  
  # Run in parallel
  results <- future_map(index_dfs, process_index, .progress = TRUE)
  results <- purrr::compact(results)  # removes NULLs
  if (length(results) == 0) return(tibble())  # or return(NULL)
  bind_rows(results)
}

Calculation

Apply the function with batch processing

plan(sequential)

Save

Look:

GAM_data

Save as an object:

Extract average values of indices per month

Extract average values of indices per month and AUC between March and October

extract_monthly_avg_indices <- function(
  GAM_data, 
  monthly_doys = list("01" = 1:31, "02" = 32:59, "03" = 60:90, "04" = 91:120, 
                      "05" = 121:151, "06" = 152:181, "07" = 182:212, 
                      "08" = 213:243, "09" = 244:273, "10" = 274:304,
                      "11" = 305:334, "12" = 335:365)) {
  
  monthly_df <- GAM_data %>%
    mutate(month = purrr::map_chr(DOY, function(doy) {
      month_name <- names(monthly_doys)[sapply(monthly_doys, 
                                               function(r) doy %in% r)]
      if (length(month_name) > 0) month_name else NA_character_
    })) %>%
    dplyr::filter(!is.na(month)) %>%
    group_by(PlotObservationID, index, month) %>%
    summarise(avg_value = mean(value, na.rm = TRUE), .groups = "drop") %>%
    mutate(avg_value = ifelse(is.infinite(avg_value), NA, avg_value)) %>%
    arrange(PlotObservationID, match(month, names(monthly_doys))) %>%
    pivot_wider(names_from = month, values_from = avg_value, 
                names_prefix = "avg_value_")
  
  # Calcular AUC entre marzo y octubre usando regla del trapecio
  months_auc <- c("03", "04", "05", "06", "07", "08", "09", "10")
  # DOY aproximado del centro de cada mes
  doy_midpoints <- c(75, 105, 135, 165, 195, 225, 255, 285)  
  
  monthly_df <- monthly_df %>%
    rowwise() %>%
    mutate(
      auc_mar_oct = {
        values <- c_across(all_of(paste0("avg_value_", months_auc)))
        if (any(is.na(values))) NA_real_ else sum(diff(doy_midpoints) *
                                                    zoo::rollmean(values, 2))
      }
    ) %>%
    ungroup()
  
  return(monthly_df)
}

Save as an object:

Assess time series quality

For the time series to be acceptable, it should have a reasonable number of time points, and these points should be distributed along almost all months (could be ok to miss the winter months).

In GAM data, check how many time points are there for each PlotObservationID, how many months, and which months are missing.

ts_quality <- GAM_data %>%
  # Filter only NDVI (all indices will have the same time points)
  dplyr::filter(index == "NDVI") %>%
  # Get month from DOY
  mutate(month = month(ymd("2020-01-01") + days(DOY - 1))) %>%
  # For each PlotObservationID
  group_by(PlotObservationID) %>%
  # Get the number of time points (days) and the number of months
  summarise(
    n_days = n_distinct(DOY),
    n_months = n_distinct(month),
    .groups = "drop"
  ) %>%
  left_join(GAM_data %>%
              # Filter only NDVI
              dplyr::filter(index == "NDVI") %>%
              # Get month from DOY
              mutate(month = month(ymd("2020-01-01") + days(DOY - 1))) %>%
              # Get unique values of PlotObservationID and month
              distinct(PlotObservationID, month) %>%
              # Add 1 as value
              mutate(value = 1) %>%
              # Reshape to wide format and add zeros when month is missing
              pivot_wider(
                names_from = month,
                names_prefix = "month",
                values_from = value,
                values_fill = 0),
            by = "PlotObservationID")

Histograms time points and n months:

ggplot(ts_quality, aes(x = n_days)) +
  geom_histogram(color = "black", fill = "white") +
  xlab("Number of time points (days) in the S2 time series") +
  theme_minimal()

ggplot(ts_quality, aes(x = n_months)) +
  geom_histogram(color = "black", fill = "white") +
  xlab("Number of months in the S2 time series") +
  theme_minimal()

Count how many PlotObservationIDs have missing data (value 0) for each month:

obs_missing_month <- ts_quality %>%
  summarise(across(starts_with("month"), ~ sum(.x == 0))) %>%
  pivot_longer(cols = everything(), names_to = "month", values_to = "nobs_missing")

ggplot(obs_missing_month %>%
         mutate(month = factor(month, levels = paste0("month", 1:12))), 
       aes(x = month, y = nobs_missing)) + geom_bar(stat = "identity") +
  ylab("Number of PlotObservationID with missing data") +
  ggtitle("Missing data in S2 time series") +
  theme_minimal()

Add quality flag:

ts_quality_flag <- ts_quality %>%
  rowwise() %>%
  mutate(
    #  If 2 consecutive months of the period March-October are missing
    # quality_flag = 0
    quality_flag = {
      months <- c_across(month3:month10)
      if (any(months[-length(months)] == 0 & months[-1] == 0)) 0 else 1
    }
  ) %>%
  ungroup()
ts_quality_flag %>% count(quality_flag)

Boxplot comparing moments for different indices

GAM_data %>% 
  select(PlotObservationID, index, sos_slope, sos_threshold, pos, eos_slope,
         eos_threshold) %>% distinct() %>%
  pivot_longer(cols = c(sos_slope, sos_threshold, pos, eos_slope, eos_threshold),
               names_to = "moment", values_to = "value") %>%
  ggplot(aes(x = moment, y = value, fill = index)) + geom_boxplot() +
  theme_minimal()

Plot fit and moments for each PlotObservationID

Quality = 1

# Get unique IDs with quality_flag == 1
ids_q1 <- ts_quality_flag %>%
  dplyr::filter(quality_flag == 1) %>%
  mutate(PlotObservationID = droplevels(PlotObservationID)) %>%
  pull(PlotObservationID)
GAM_data_ids_q1 <- GAM_data %>%
  # Join to get biogeo and unit
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, biogeo, unit) %>%
              distinct()) %>%
  # Join to get EUNIS info
   left_join(db_Europa_allobs %>%
               select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
                      EUNISa_2, EUNISa_2_descr)) %>%
  # Join to get original values of indices
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, DOY, NDVI, EVI, SAVI) %>%
              pivot_longer(cols = c(NDVI, EVI, SAVI), names_to = "index", 
                           values_to = "value_orig")) %>%
  # Join to get ts_quality data
  left_join(ts_quality_flag %>% select(PlotObservationID, quality_flag)) %>%
  # Keep only those with quality_flag == 1
  dplyr::filter(quality_flag == 1)

Save each plot to a file:

Quality = 0

# Get unique IDs with quality_flag == 0
ids_q0 <- ts_quality_flag %>%
  dplyr::filter(quality_flag == 0) %>%
  mutate(PlotObservationID = droplevels(PlotObservationID)) %>%
  pull(PlotObservationID)
GAM_data_ids_q0 <- GAM_data %>%
  # Join to get biogeo and unit
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, biogeo, unit) %>%
              distinct()) %>%
  # Join to get EUNIS info
   left_join(db_Europa_allobs %>%
               select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
                      EUNISa_2, EUNISa_2_descr)) %>%
  # Join to get original values of indices
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, DOY, NDVI, EVI, SAVI) %>%
              pivot_longer(cols = c(NDVI, EVI, SAVI), names_to = "index", 
                           values_to = "value_orig")) %>%
  # Join to get ts_quality data
  left_join(ts_quality_flag %>%
              select(PlotObservationID, n_months, quality_flag)) %>%
  # Keep only those with quality_flag == 0
  dplyr::filter(quality_flag == 0)

Save each plot to a file:

Smooth the time series of NDMI and NDWI

Using GAM, without replacing values in DOY 1–50 and DOY 315–end with separate base values, later use only unweighted GAM.

compute_unweighted_fit <- function(
    # Data frame df with index values over time (DOY)
    df, 
    # Name of the vegetation indices columns (e.g., "NDVI", "EVI", "SAVI)
    index_cols = c("NDMI", "NDWI")
) {
  # Initialize list to store results
  fits_list <- list()
  
  # Loop over each index column
  for (index_col in index_cols) {
    df_index <- df %>%
      # Remove rows with missing index values and sort data by DOY
      filter(!is.na(.data[[index_col]])) %>% arrange(DOY)
    
    # Extract x (DOY) and y (index) vectors for modelling
    x <- df_index$DOY
    y <- df_index[[index_col]]
    
    # If there are fewer than 11 observations or all values are NA, skip
    if (length(x) < 11 || all(is.na(y))) {
      next
    }
    
    # Fit GAM (unweighted) with a thin plate spline (bs = "tp")
    # to smooth the index curve
    gam_unweighted <- mgcv::bam(y ~ s(x, bs = "tp"))
    pred <- predict(gam_unweighted, newdata = tibble(x = x))
    
    # Create tibble to store original and predicted index values
    fits_df <- tibble(
      PlotObservationID = unique(df$PlotObservationID),
      DOY = x,
      index = index_col,
      value = pred
    )
    
    fits_list[[index_col]] <- fits_df
  }
  
  if (length(fits_list) == 0) {
    return(tibble())
  }
  
  bind_rows(fits_list)
}

Apply the function:

Look:

smoothed_data

Save as an object:

Plot fit and moments for each PlotObservationID

smoothed_data_ids <- smoothed_data %>%
  # Join to get biogeo and unit
  left_join(data_RS_S2_bands_indices %>%
              select(PlotObservationID, biogeo, unit) %>%
              distinct()) %>%
  # Join to get EUNIS info
   left_join(db_Europa_allobs %>%
               select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
                      EUNISa_2, EUNISa_2_descr)) %>%
  mutate(PlotObservationID = as.character(PlotObservationID))

Save each plot to a file:

Get indices data (max. and min.)

Careful! These maximum and minimum values are from the smoothed time series. For NDVI / EVI / SAVI values in DOY 1–50 and DOY 315–end, remember that the GAM smoothing function replaced the original values with the mean base value of observations during each of these respective periods. This was so far not done for NDMI and NDWI.

final_indices_data <- GAM_data %>%
  group_by(PlotObservationID, index) %>%
  summarise(max = max(value), min = min(value)) %>%
  ungroup() %>%
  pivot_wider(names_from = index, values_from = c(max, min),
              names_glue = "{index}_{.value}") %>%
  full_join(
    smoothed_data %>%
      group_by(PlotObservationID, index) %>%
      summarise(max = max(value), min = min(value)) %>%
      ungroup() %>%
      pivot_wider(names_from = index, values_from = c(max, min),
                  names_glue = "{index}_{.value}")
    )

Get phenology data

Use GAM iter_3 to get dates of the moments, values at those moments and AUC (time-integrated indices) between SOS and EOS:

# Join to get values at SOS, POS, EOS and auc
final_phenology_data <- GAM_data %>%
  mutate(
    stage = case_when(
      DOY == sos_slope ~ "sos_slope",
      DOY == sos_threshold ~ "sos_threshold",
      DOY == pos ~ "pos",
      DOY == eos_slope ~ "eos_slope",
      DOY == eos_threshold ~ "eos_threshold",
      TRUE ~ NA_character_
    )
  ) %>%
  dplyr::filter(!is.na(stage)) %>%
  select(PlotObservationID, index, stage, doy = DOY, value) %>%
  pivot_wider(
    names_from = c(index, stage),
    values_from = c(doy, value),
    names_glue = "{index}_{stage}_{.value}"
  ) %>%
  # Convert list cols to regular numeric cols
  mutate(
    NDVI_sos_slope_value = map_dbl(NDVI_sos_slope_value, 1),
    NDVI_sos_threshold_value = map_dbl(NDVI_sos_threshold_value, 1),
    NDVI_pos_value = map_dbl(NDVI_pos_value, 1),
    NDVI_eos_slope_value = map_dbl(NDVI_eos_slope_value, 1),
    NDVI_eos_threshold_value = map_dbl(NDVI_eos_threshold_value, 1),
    EVI_sos_slope_value = map_dbl(EVI_sos_slope_value, 1),
    EVI_sos_threshold_value = map_dbl(EVI_sos_threshold_value, 1),
    EVI_pos_value = map_dbl(EVI_pos_value, 1),
    EVI_eos_slope_value = map_dbl(EVI_eos_slope_value, 1),
    EVI_eos_threshold_value = map_dbl(EVI_eos_threshold_value, 1),
    SAVI_sos_slope_value = map_dbl(SAVI_sos_slope_value, 1),
    SAVI_sos_threshold_value = map_dbl(SAVI_sos_threshold_value, 1),
    SAVI_pos_value = map_dbl(SAVI_pos_value, 1),
    SAVI_eos_slope_value = map_dbl(SAVI_eos_slope_value, 1),
    SAVI_eos_threshold_value = map_dbl(SAVI_eos_threshold_value, 1)
  ) %>%
  full_join(GAM_data %>%
              distinct(PlotObservationID, index, auc_slope, auc_threshold) %>%
              pivot_wider(names_from = index, values_from = c(auc_slope, auc_threshold),
                          names_glue = "{index}_{.value}"))

Join indices and phenology data

final_RS_data <- full_join(
  # Indices data (max and min)
  final_indices_data,
  # Average values of indices per month
  monthly_avg_indices %>%
    pivot_wider(names_from = index, values_from = c(avg_value_01:auc_mar_oct),
                names_glue = "{index}_{.value}")
  ) %>%
  full_join(
    # Phenology data
    final_phenology_data 
    ) %>%
  # Sort cols in alphabetical order
  select(PlotObservationID, sort(names(.)[names(.) != "PlotObservationID"]))

Add EUNIS codes

final_RS_data <- final_RS_data %>% left_join(db_Europa_allobs)
data_RS_S2_bands_indices <- data_RS_S2_bands_indices %>%
  left_join(db_Europa_allobs)

Monthly spectrophenology per habitat type

# Prepare the data
data_monthly_EUNISa_1 <- data_RS_S2_bands_indices %>%
  mutate(month = month(date, label = TRUE, abbr = TRUE, locale="EN-us")) %>%
  group_by(month, EUNISa_1, EUNISa_1_descr) %>%
  summarise(
    mean_NDVI = mean(NDVI, na.rm = TRUE),
    sd_NDVI = sd(NDVI, na.rm = TRUE),
    n_NDVI = sum(!is.na(NDVI)),
    mean_EVI = mean(EVI, na.rm = TRUE),
    sd_EVI = sd(EVI, na.rm = TRUE),
    n_EVI = sum(!is.na(EVI)),
    mean_SAVI = mean(SAVI, na.rm = TRUE),
    sd_SAVI = sd(SAVI, na.rm = TRUE),
    n_SAVI = sum(!is.na(SAVI)),
    .groups = "drop"
  )

data_monthly_EUNISa_1 <- data_monthly_EUNISa_1 %>%
  # Add label with n
  left_join(data_monthly_EUNISa_1 %>%
              group_by(EUNISa_1) %>%
              summarise(n_total = sum(n_NDVI, na.rm = TRUE)), 
            by = "EUNISa_1") %>%
  mutate(EUNISa_1_label = paste0(EUNISa_1, " (n = ", n_total, ")"))

data_monthly_EUNISa_2 <- data_RS_S2_bands_indices %>%
  mutate(month = month(date, label = TRUE, abbr = TRUE, locale="EN-us")) %>%
  group_by(month, EUNISa_1, EUNISa_1_descr, EUNISa_2, EUNISa_2_descr) %>%
  summarise(
    mean_NDVI = mean(NDVI, na.rm = TRUE),
    sd_NDVI = sd(NDVI, na.rm = TRUE),
    n_NDVI = sum(!is.na(NDVI)),
    mean_EVI = mean(EVI, na.rm = TRUE),
    sd_EVI = sd(EVI, na.rm = TRUE),
    n_EVI = sum(!is.na(EVI)),
    mean_SAVI = mean(SAVI, na.rm = TRUE),
    sd_SAVI = sd(SAVI, na.rm = TRUE),
    n_SAVI = sum(!is.na(SAVI)),
    .groups = "drop"
  )

data_monthly_EUNISa_2 <- data_monthly_EUNISa_2 %>%
  # Add label with n
  left_join(data_monthly_EUNISa_2 %>%
              group_by(EUNISa_2) %>%
              summarise(n_total = sum(n_NDVI, na.rm = TRUE)), 
            by = "EUNISa_2") %>%
  mutate(EUNISa_2_label = paste0(EUNISa_2, " (n = ", n_total, ")"))

EUNIS level 1

# Plots

# EUNISa_1
ggplot(data_monthly_EUNISa_1 %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_1_label, EUNISa_1_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_1, EUNISa_1_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, 
           group = EUNISa_1_label)) +
  geom_point() +
  geom_line(aes(group = EUNISa_1)) +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
  #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNIS1)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS1_NDVI.jpeg"),
  dpi = 300, width = 6, height = 2.5)


ggplot(data_monthly_EUNISa_1 %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_1_label, EUNISa_1_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_1, EUNISa_1_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, 
           group = EUNISa_1_label)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNIS1)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS1_EVI.jpeg"),
  dpi = 300, width = 6, height = 2.5)


ggplot(data_monthly_EUNISa_1 %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_1_label, EUNISa_1_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_1, EUNISa_1_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, 
           group = EUNISa_1_label)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNIS1)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS1_SAVI.jpeg"),
  dpi = 300, width = 6, height = 2.5)

EUNIS level 2

Q

# NDVI
ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
         # Remove those with EUNIS level 2 that does not match current classif
         dplyr::filter(EUNISa_2 != "Qa" & EUNISa_2 != "Qb") %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_Q_NDVI.jpeg"),
  dpi = 300, width = 7, height = 2.5)


# EVI
ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
         # Remove those with EUNIS level 2 that does not match current classif
         dplyr::filter(EUNISa_2 != "Qa" & EUNISa_2 != "Qb") %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_Q_EVI.jpeg"),
  dpi = 300, width = 7, height = 2.5)


# SAVI
ggplot(data_monthly_EUNISa_2 %>%
         dplyr::filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
         # Remove those with EUNIS level 2 that does not match current classif
         dplyr::filter(EUNISa_2 != "Qa" & EUNISa_2 != "Qb") %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_Q_SAVI.jpeg"),
  dpi = 300, width = 7, height = 2.5)

R

ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_R_NDVI.jpeg"),
  dpi = 300, width = 7, height = 2.5)


ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_R_EVI.jpeg"),
  dpi = 300, width = 7, height = 2.5)


ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_R_SAVI.jpeg"),
  dpi = 300, width = 7, height = 2.5)

S

ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
         # Remove those with EUNIS level 2 that does not match current classif
         dplyr::filter(EUNISa_2 != "Sa" & EUNISa_2 != "Sb") %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_S_NDVI.jpeg"),
  dpi = 300, width = 8, height = 2.5)


ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
         # Remove those with EUNIS level 2 that does not match current classif
         dplyr::filter(EUNISa_2 != "Sa" & EUNISa_2 != "Sb") %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_S_EVI.jpeg"),
  dpi = 300, width = 8, height = 2.5)


ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
         # Remove those with EUNIS level 2 that does not match current classif
         dplyr::filter(EUNISa_2 != "Sa" & EUNISa_2 != "Sb") %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_S_SAVI.jpeg"),
  dpi = 300, width = 8, height = 2.5)

T

ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI), 
                #width = 0.2) +
  labs(
    title = "Monthly NDVI by Habitat Type",
    x = "Month",
    y = "NDVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_T_NDVI.jpeg"),
  dpi = 300, width = 6, height = 2.5)


ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI), 
                #width = 0.2) +
  labs(
    title = "Monthly EVI by Habitat Type",
    x = "Month",
    y = "EVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_T_EVI.jpeg"),
  dpi = 300, width = 6, height = 2.5)


ggplot(data_monthly_EUNISa_2 %>% 
         dplyr::filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
         # If we want to have n points
         # mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")), 
         mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")), 
       aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
  geom_point() +
  geom_line() +
  #geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI), 
                #width = 0.2) +
  labs(
    title = "Monthly SAVI by Habitat Type",
    x = "Month",
    y = "SAVI",
    color = "Habitat (EUNIS2)"
  ) +
  theme_minimal()
ggsave(
  here("output", "figures", "monthly_spectrophenology", "EUNIS2_T_SAVI.jpeg"),
  dpi = 300, width = 6, height = 2.5)

Calculate other phenological metrics

final_RS_data <- final_RS_data %>%
  mutate(
    # with slope method
    # Growing season duration
    NDVI_slope_gsd = NDVI_eos_slope_doy - NDVI_sos_slope_doy,
    EVI_slope_gsd = NDVI_eos_slope_doy - NDVI_sos_slope_doy,
    SAVI_slope_gsd = SAVI_eos_slope_doy - SAVI_sos_slope_doy,
    # Difference in value between pos and sos
    NDVI_diff_pos_sos_slope_value = NDVI_pos_value - NDVI_sos_slope_value,
    EVI_diff_pos_sos_slope_value = EVI_pos_value - EVI_sos_slope_value,
    SAVI_diff_pos_sos_slope_value = SAVI_pos_value - SAVI_sos_slope_value,
    # Difference in value between pos and eos
    NDVI_diff_pos_eos_slope_value = NDVI_pos_value - NDVI_eos_slope_value,
    EVI_diff_pos_eos_slope_value = EVI_pos_value - EVI_eos_slope_value,
    SAVI_diff_pos_eos_slope_value = SAVI_pos_value - SAVI_eos_slope_value,
    # Difference in doy between pos and sos
    NDVI_diff_pos_sos_slope_doy = NDVI_pos_doy - NDVI_sos_slope_doy,
    EVI_diff_pos_sos_slope_doy = EVI_pos_doy - EVI_sos_slope_doy,
    SAVI_diff_pos_sos_slope_doy = SAVI_pos_doy - SAVI_sos_slope_doy,
    # Absolute difference in doy between pos and eos
    NDVI_diff_pos_eos_slope_doy = abs(NDVI_pos_doy - NDVI_eos_slope_doy),
    EVI_diff_pos_eos_slope_doy = abs(EVI_pos_doy - EVI_eos_slope_doy),
    SAVI_diff_pos_eos_slope_doy = abs(SAVI_pos_doy - SAVI_eos_slope_doy),
    # With threshold method
    # Growing season duration
    NDVI_threshold_gsd = NDVI_eos_threshold_doy - NDVI_sos_threshold_doy,
    EVI_threshold_gsd = NDVI_eos_threshold_doy - NDVI_sos_threshold_doy,
    SAVI_threshold_gsd = SAVI_eos_threshold_doy - SAVI_sos_threshold_doy,
    # Difference in value between pos and sos
    NDVI_diff_pos_sos_threshold_value =
      NDVI_pos_value - NDVI_sos_threshold_value,
    EVI_diff_pos_sos_threshold_value = 
      EVI_pos_value - EVI_sos_threshold_value,
    SAVI_diff_pos_sos_threshold_value = 
      SAVI_pos_value - SAVI_sos_threshold_value,
    # Difference in value between pos and eos
    NDVI_diff_pos_eos_threshold_value =
      NDVI_pos_value - NDVI_eos_threshold_value,
    EVI_diff_pos_eos_threshold_value = 
      EVI_pos_value - EVI_eos_threshold_value,
    SAVI_diff_pos_eos_threshold_value = 
      SAVI_pos_value - SAVI_eos_threshold_value,
    # Difference in doy between pos and sos
    NDVI_diff_pos_sos_threshold_doy = NDVI_pos_doy - NDVI_sos_threshold_doy,
    EVI_diff_pos_sos_threshold_doy = EVI_pos_doy - EVI_sos_threshold_doy,
    SAVI_diff_pos_sos_threshold_doy = SAVI_pos_doy - SAVI_sos_threshold_doy,
    # Absolute difference in doy between pos and eos
    NDVI_diff_pos_eos_threshold_doy = 
      abs(NDVI_pos_doy - NDVI_eos_threshold_doy),
    EVI_diff_pos_eos_threshold_doy = 
      abs(EVI_pos_doy - EVI_eos_threshold_doy),
    SAVI_diff_pos_eos_threshold_doy = 
      abs(SAVI_pos_doy - SAVI_eos_threshold_doy),
    # With months method
    # Difference in value between pos and march
    NDVI_diff_pos_march_value = NDVI_pos_value - NDVI_avg_value_03,
    EVI_diff_pos_march_value = EVI_pos_value - EVI_avg_value_03,
    SAVI_diff_pos_march_value = SAVI_pos_value - SAVI_avg_value_03,
    # Difference in value between pos and oct
    NDVI_diff_pos_oct_value = NDVI_pos_value - NDVI_avg_value_10,
    EVI_diff_pos_oct_value = EVI_pos_value - EVI_avg_value_10,
    SAVI_diff_pos_oct_value = SAVI_pos_value - SAVI_avg_value_10,
    # Difference in doy between pos and march
    NDVI_diff_pos_march_doy = NDVI_pos_doy - 75, 
    EVI_diff_pos_march_doy = EVI_pos_doy - 75,
    SAVI_diff_pos_march_doy = SAVI_pos_doy - 75,
    # Difference in doy between pos and oct
    NDVI_diff_pos_oct_doy = abs(NDVI_pos_doy - 285),
    EVI_diff_pos_oct_doy = abs(EVI_pos_doy - 285),
    SAVI_diff_pos_oct_doy = abs(SAVI_pos_doy - 285)
  )

Checks

Slope method

# Growing season duration should be positive
nrow(final_RS_data %>% 
       dplyr::filter(NDVI_slope_gsd <= 0 | EVI_slope_gsd <= 0 | 
                       SAVI_slope_gsd <= 0))
[1] 0
# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_sos_slope_value <= 0))
[1] 108
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_sos_slope_value <= 0))
[1] 91
nrow(final_RS_data %>%
       dplyr::filter(SAVI_diff_pos_sos_slope_value <= 0))
[1] 47
# Difference in value between pos and eos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_eos_slope_value <= 0))
[1] 340
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_eos_slope_value <= 0))
[1] 230
nrow(final_RS_data %>%
       dplyr::filter(SAVI_diff_pos_eos_slope_value <= 0))
[1] 78
# Difference in doy between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_sos_slope_doy <= 0 |
                       EVI_diff_pos_sos_slope_doy <= 0 |
                       SAVI_diff_pos_sos_slope_doy <= 0))
[1] 0
# Difference in doy between eos and pos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_eos_slope_doy <= 0 |
                       EVI_diff_pos_eos_slope_doy <= 0 |
                       SAVI_diff_pos_eos_slope_doy <= 0))
[1] 0

Threshold method

# Growing season duration should be positive
nrow(final_RS_data %>% 
       dplyr::filter(NDVI_threshold_gsd <= 0 | EVI_threshold_gsd <= 0 | 
                       SAVI_threshold_gsd <= 0))
[1] 0
# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_sos_threshold_value <= 0))
[1] 191
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_sos_threshold_value <= 0))
[1] 188
nrow(final_RS_data %>%
       dplyr::filter(SAVI_diff_pos_sos_threshold_value <= 0))
[1] 78
# Difference in value between pos and eos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_eos_threshold_value <= 0))
[1] 218
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_eos_threshold_value <= 0))
[1] 288
nrow(final_RS_data %>%
       dplyr::filter(SAVI_diff_pos_eos_threshold_value <= 0))
[1] 79
# Difference in doy between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_sos_threshold_doy <= 0 |
                       EVI_diff_pos_sos_threshold_doy <= 0 |
                       SAVI_diff_pos_sos_threshold_doy <= 0))
[1] 31
# Difference in doy between eos and pos should be positive
nrow(final_RS_data %>%
       dplyr::filter(NDVI_diff_pos_eos_threshold_doy <= 0 |
                       EVI_diff_pos_eos_threshold_doy <= 0 |
                       SAVI_diff_pos_eos_threshold_doy <= 0))
[1] 0

HERE: Run. Detect number of peaks in smoothed curves

# Set up parallel plan
plan(multisession, workers = min(parallel::detectCores() - 1))

# Define peak-counting function
count_peaks <- function(df) {
  # Convert 1D array column to numeric vector
  y <- as.numeric(df$value)
  
  peaks <- findpeaks(y, minpeakdistance = 30, threshold = 0.02)
  
  tibble(
    PlotObservationID = unique(df$PlotObservationID),
    index = unique(df$index),
    num_peaks = if (!is.null(peaks)) nrow(peaks) else 0
  )
}

# Apply peak counting in parallel
peak_counts <- GAM_data %>%
  filter(fit_type == "iter_3") %>%
  arrange(DOY) %>%
  group_by(PlotObservationID, index) %>%
  nest() %>%
  mutate(result = future_map(data, count_peaks, .progress = TRUE,
                             .options = furrr_options(scheduling = Inf))) %>%
  select(-data) %>%
  unnest(result)

# Summarize result
peak_counts_summary <- peak_counts %>% count(index, num_peaks)

HERE: save

# # Function to count peaks for each PlotObservationID
# count_peaks <- function(df) {
#   y <- df$value
#   peaks <- findpeaks(y, 
#                      # Minimum number of indices (e.g., DOY steps)
#                      # between two peaks
#                      minpeakdistance = 30, 
#                      # Minimum vertical difference between a peak
#                      # and its surrounding value
#                      threshold = 0.02)
#   num_peaks <- if (!is.null(peaks)) nrow(peaks) else 0
#   return(tibble(PlotObservationID = unique(df$PlotObservationID),
#                 num_peaks = num_peaks))
# }
# 
# # Apply to each group
# peak_counts <- GAM_data %>%
#   mutate(value = map_dbl(value, 1)) %>%
#   dplyr::filter(fit_type == "iter_3") %>%
#   arrange(DOY) %>%
#   group_by(PlotObservationID, index) %>%
#   group_modify(~ count_peaks(.x)) %>%
#   ungroup()
# 
# # View result
# peak_counts %>% count(index, num_peaks)

Plot number of peaks

peak_counts %>% count(index, num_peaks) %>%
  ggplot(aes(x = index, y = n, fill = factor(num_peaks))) +
  geom_bar(stat = "identity", position = position_dodge())

EVI gives less problems, maybe use only this one?

Add number of peaks to data

final_RS_data <- final_RS_data %>%
  left_join(peak_counts %>%
              pivot_wider(names_from = index, values_from = num_peaks,
                          names_glue = "{index}_{.value}"))

Plot fit and moments for PlotObservationIDs with zero peaks

Further checks (EVI)

Slope method

# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
       dplyr::filter(EVI_diff_pos_sos_slope_value <= 0))
[1] 91
final_RS_data %>%
  dplyr::filter(EVI_diff_pos_sos_slope_value <= 0) %>%
  count(EVI_num_peaks)
Error in `count()`:
! Must group by variables found in `.data`.
✖ Column `EVI_num_peaks` is not found.
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.

Threshold method

Add some columns needed

final_RS_data <- final_RS_data %>%
  left_join(
    data_RS_S2_bands_indices %>%
      distinct(PlotObservationID, year, biogeo, unit, Lctnmth)
    )

Add canopy height data

Read the data:

data_RS_CH <- read_csv(
  "C:/Data/MOTIVATE/MOTIVATE_RS_data/Canopy_Height_1m/Europe_points_CanopyHeight_1m.csv")
db_Europa <- read_csv(
  here("..", "DB_first_check", "data", "clean","db_Europa_20250107.csv")
  )
data_RS_CH_ID <- db_Europa %>%
  select(PlotObservationID, obs_unique_id) %>%
  right_join(data_RS_CH %>%
              # Rename to be able to join on this column
              rename(obs_unique_id = obs_unique)) %>%
  select(PlotObservationID, canopy_height)

Join:

final_RS_data <- final_RS_data %>%
  left_join(data_RS_CH_ID %>%
              mutate(PlotObservationID = factor(PlotObservationID)))

Save to clean data

write_tsv(final_RS_data,
          here("data", "clean","final_RS_data_bands_S2_all_20250804.csv"))

Session info

sessionInfo()
R version 4.5.1 (2025-06-13 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=Spanish_Spain.utf8  LC_CTYPE=Spanish_Spain.utf8    LC_MONETARY=Spanish_Spain.utf8
[4] LC_NUMERIC=C                   LC_TIME=Spanish_Spain.utf8    

time zone: Europe/Madrid
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] beepr_2.0        pracma_2.4.4     progressr_0.15.1 furrr_0.3.1      future_1.67.0   
 [6] mgcv_1.9-3       nlme_3.1-168     knitr_1.50       sf_1.0-21        dtplyr_1.3.2    
[11] here_1.0.2       lubridate_1.9.4  forcats_1.0.0    stringr_1.5.2    dplyr_1.1.4     
[16] purrr_1.1.0      readr_2.1.5      tidyr_1.3.1      tibble_3.3.0     ggplot2_4.0.0   
[21] tidyverse_2.0.0  signal_1.8-1    

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.53          lattice_0.22-7     tzdb_0.5.0        
 [5] vctrs_0.6.5        tools_4.5.1        generics_0.1.4     parallel_4.5.1    
 [9] proxy_0.4-27       pkgconfig_2.0.3    Matrix_1.7-4       KernSmooth_2.23-26
[13] data.table_1.17.8  RColorBrewer_1.1-3 S7_0.2.0           lifecycle_1.0.4   
[17] compiler_4.5.1     farver_2.1.2       textshaping_1.0.3  codetools_0.2-20  
[21] htmltools_0.5.8.1  class_7.3-23       yaml_2.3.10        crayon_1.5.3      
[25] pillar_1.11.0      MASS_7.3-65        classInt_0.4-11    parallelly_1.45.1 
[29] tidyselect_1.2.1   digest_0.6.37      stringi_1.8.7      listenv_0.9.1     
[33] labeling_0.4.3     splines_4.5.1      rprojroot_2.1.1    fastmap_1.2.0     
[37] grid_4.5.1         cli_3.6.5          magrittr_2.0.3     utf8_1.2.6        
[41] e1071_1.7-16       withr_3.0.2        scales_1.4.0       bit64_4.6.0-1     
[45] timechange_0.3.0   rmarkdown_2.29     audio_0.1-11       globals_0.18.0    
[49] bit_4.6.0          ragg_1.5.0         hms_1.1.3          evaluate_1.0.5    
[53] rlang_1.1.6        Rcpp_1.1.0         glue_1.8.0         DBI_1.2.3         
[57] vroom_1.6.5        rstudioapi_0.17.1  R6_2.6.1           systemfonts_1.2.3 
[61] units_0.8-7       
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB3b3JrIHdpdGggUzIgYmFuZHMgZGVyaXZlZCBmcm9tIEdFRSINCnN1YnRpdGxlOiAiUmVhZCBhbmQgbWFuaXB1bGF0aW9uIGRhdGEsIGNhbGN1bGF0ZSBpbmRpY2VzIg0KYXV0aG9yOiAiQWxpY2lhIFZhbGTDqXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpDQpgYGANCg0KIyBMb2FkIGxpYnJhcmllcw0KDQpgYGB7cn0NCmxpYnJhcnkoc2lnbmFsKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoZHRwbHlyKQ0KbGlicmFyeShzZikNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KG1nY3YpDQpsaWJyYXJ5KGZ1dHVyZSkNCmxpYnJhcnkoZnVycnIpDQpsaWJyYXJ5KHByb2dyZXNzcikNCmxpYnJhcnkocHJhY21hKQ0KYGBgDQoNCiMgU2V0IGEgc2ltcGxlIGNvbnNvbGUgcHJvZ3Jlc3MgYmFyDQoNCmBgYHtyfQ0KaGFuZGxlcnMoInR4dHByb2dyZXNzYmFyIikgIyBTaW1wbGUgY29uc29sZSBwcm9ncmVzcyBiYXINCmBgYA0KDQojIFN1cHJlc3MgcGFja2FnZSBtZXNzYWdlcw0KDQpgYGB7cn0NCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7DQogIGxpYnJhcnkobWdjdikNCiAgbGlicmFyeShubG1lKQ0KfSkNCmBgYA0KDQojIExvYWQgcHJldmlvdXNseSBjcmVhdGVkIG9iamVjdHMNCg0KYGBge3J9DQpsb2FkKGZpbGUgPSAib2JqZWN0cy9kYXRhX1JTX1MyX2JhbmRzX2luZGljZXMuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvR0FNX2RhdGFfUzIuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvc21vb3RoZWRfZGF0YV9TMi5SZGF0YSIpDQpsb2FkKGZpbGUgPSAib2JqZWN0cy9tb250aGx5X2F2Z19pbmRpY2VzX1MyLlJkYXRhIikNCmBgYA0KDQojIExvYWQgUmVzdXJ2ZXkgZGINCg0KYGBge3J9DQpkYl9FdXJvcGFfYWxsb2JzIDwtIHJlYWRfY3N2KA0KICBoZXJlKCJkYXRhIiwgImNsZWFuIiwgImRiX0V1cm9wYV9hbGxvYnMuY3N2IikpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjciwNCiAgICAgICAgIEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjcikgJT4lDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGZhY3RvcihQbG90T2JzZXJ2YXRpb25JRCksDQogICAgICAgICBFVU5JU2FfMSA9IGZhY3RvcihFVU5JU2FfMSksIEVVTklTYV8yID0gZmFjdG9yKEVVTklTYV8yKSkNCmBgYA0KDQojIERlZmluZSBwcmludGFsbCBmdW5jdGlvbg0KDQpgYGB7cn0NCnByaW50YWxsIDwtIGZ1bmN0aW9uKHRpYmJsZSkgew0KICBwcmludCh0aWJibGUsIHdpZHRoID0gSW5mKQ0KICB9DQpgYGANCg0KIyBSZWFkIGZpbGVzIHdpdGggYmFuZCBkYXRhDQoNCkkgZ290IHRoZXNlIGZpbGVzIHVzaW5nIHRoZSBHRUUgY29kZSBwcmVwYXJlZCBieSBCZWEuDQoNClRoZXNlIGZpbGVzIGNvbnRhaW4gYWxsIG9ic2VydmF0aW9ucyBpbiB0aGUgUmVTdXJ2ZXkgZGF0YWJhc2UgZnJvbSAyMDE2IG9ud2FyZC4gSW4gb3JkZXIgdG8gYXZvaWQgY29tcHV0YXRpb24gcHJvYmxlbXMgaW4gR0VFLCBiaW9nZW9ncmFwaGljYWwgdW5pdHMgdGhhdCBjb250YWluIG1vcmUgdGhhbiA0NTAwIHBvaW50cyBoYXZlIGJlZW4gc3ViZGl2aWRlZCBpbiBBcmNHSVMuDQoNCmBgYHtyfQ0KIyBTZXQgdGhlIGZvbGRlciBwYXRoDQpmb2xkZXJfcGF0aCA8LSAiQzovRGF0YS9NT1RJVkFURS9NT1RJVkFURV9SU19kYXRhL1MyL0JhbmRzL2FsbCINCg0KIyBMaXN0IENTViBmaWxlcw0KY3N2X2ZpbGVzIDwtIGxpc3QuZmlsZXMoZm9sZGVyX3BhdGgsIGZ1bGwubmFtZXMgPSBUUlVFLCByZWN1cnNpdmUgPSBUUlVFKQ0KDQojIEZ1bmN0aW9uIHRvIGV4dHJhY3QgYmlvZ2VvIGFuZCB1bml0IGZyb20gdGhlIGZpbGVuYW1lDQpleHRyYWN0X2luZm8gPC0gZnVuY3Rpb24oZmlsZW5hbWUpIHsNCiAgZmlyc3Rfd29yZCA8LSBzdHJzcGxpdChmaWxlbmFtZSwgIl8iKVtbMV1dWzFdDQogIGJpb2dlbyA8LSBzdHJfZXh0cmFjdChmaXJzdF93b3JkLA0KICAgICAgICAgICAgICAgICAgICAgICAgIl4oQUxQfEFOQXxBUkN8QVRMfEJMQUNLU0VBfEJPUnxDT058TUFDQVJPTkVTSUF8TUVEfFBBTk9OSUF8U1RFUFBJQykiKQ0KICB1bml0IDwtIHN0cl9yZW1vdmUoZmlyc3Rfd29yZCwgYmlvZ2VvKQ0KICBpZiAoaXMubmEodW5pdCkgfHwgdW5pdCA9PSAiIikgdW5pdCA8LSBOQV9jaGFyYWN0ZXJfDQogIGxpc3QoYmlvZ2VvID0gYmlvZ2VvLCB1bml0ID0gdW5pdCkNCiAgfQ0KDQoNCiMgRGVmaW5lIGNvbHVtbiB0eXBlczogZm9yY2UgUlNydnlwbCB0byBjaGFyYWN0ZXIsIG90aGVycyBhdXRvLWRldGVjdGVkDQpjdXN0b21fY29sX3R5cGVzIDwtIGNvbHMoDQogIFJTcnZ5cGwgPSBjb2xfY2hhcmFjdGVyKCksDQogIFJTcnZ5c3QgPSBjb2xfY2hhcmFjdGVyKCksDQogIGRlZmF1bHQgPSBjb2xfZ3Vlc3MoKQ0KKQ0KDQojIFJlYWQgYW5kIHByb2Nlc3MgZWFjaCBmaWxlDQpkYXRhX2xpc3QgPC0gbGFwcGx5KGNzdl9maWxlcywgZnVuY3Rpb24oZmlsZSkgew0KICBpbmZvIDwtIGV4dHJhY3RfaW5mbyhiYXNlbmFtZShmaWxlKSkgIyBVc2Ugb25seSB0aGUgZmlsZW5hbWUNCiAgDQogICMgUmVhZCB0aGUgZmlsZQ0KICBkZiA8LSByZWFkX2NzdihmaWxlLCBjb2xfdHlwZXMgPSBjdXN0b21fY29sX3R5cGVzKSAlPiUNCiAgICAjIFJlbW92ZSBjb2x1bW5zIHRoYXQgZ2l2ZSBjb2x1bW4gdHlwZSBwcm9ibGVtcyB3aGVuIGNvbWJpbmluZyBkYXRhDQogICAgc2VsZWN0KC1zdGFydHNfd2l0aCgiRVVOSVMiKSwgLXN0YXJ0c193aXRoKCJSZVN1cnZleSIpKSAlPiUNCiAgICBtdXRhdGUoYmlvZ2VvID0gaW5mbyRiaW9nZW8sIHVuaXQgPSBpbmZvJHVuaXQpDQogIA0KICByZXR1cm4oZGYpDQogIH0pDQoNCiMgQ29tYmluZSBhbGwgZGF0YQ0KZGF0YV9SU19TMl9iYW5kcyA8LSBiaW5kX3Jvd3MoZGF0YV9saXN0KSAlPiUNCiAgcmVuYW1lKFBsb3RPYnNlcnZhdGlvbklEID0gUGx0T2JJRCkNCg0KIyBWaWV3IHRoZSByZXN1bHRpbmcgdGliYmxlDQpwcmludChkYXRhX1JTX1MyX2JhbmRzKQ0KDQojIENvdW50cyBwZXIgYmlvZ2VvIGFuZCB1bml0DQpwcmludChkYXRhX1JTX1MyX2JhbmRzICU+JSBjb3VudChiaW9nZW8sIHVuaXQpLCBuID0gMTAwKQ0KYGBgDQoNCiMgU29tZSBjaGVja3MNCg0KQ2hlY2sgdGhhdCB0aGUgeWVhciBpbiB0aGUgZGF0ZSBvZiB0aGUgaW1hZ2VzIGlzIG5vdCBkaWZmZXJlbnQgdG8gdGhlIHNhbXBsaW5nIHllYXI6DQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kcyAlPiUgZHBseXI6OmZpbHRlcih5ZWFyICE9IHllYXIoZGF0ZSkpDQpgYGANCg0KQ2hlY2sgaG93IG1hbnkgZGlmZmVyZW50IGltYWdlcyBhcmUgZm9yIGVhY2ggb2JzZXJ2YXRpb24sIGRhdGUgYW5kIHRpbWU6DQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kcyAlPiUgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGRhdGUsIHRpbWVfdXRjKSAlPiUNCiAgc3VtbWFyaXNlKG5faW1hZ2VzID0gbl9kaXN0aW5jdChpbWFnZV9pZCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBjb3VudChuX2ltYWdlcykNCmBgYA0KDQojIEF2ZXJhZ2UgdGhlIGJhbmRzDQoNCldoZW4gdGhlcmUgaXMgbW9yZSB0aGFuIG9uZSBpbWFnZSBmb3IgZWFjaCBwb2ludCBhbmQgZGF5LCBhdmVyYWdlIHRoZSB2YWx1ZXMgb2YgdGhlIGJhbmRzOg0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBTdW1tYXJpemUgdGhlIGJhbmQgdmFsdWVzIGNvbmRpdGlvbmFsbHkNCmJhbmRfc3VtbWFyeSA8LSBkYXRhX1JTX1MyX2JhbmRzICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgZGF0ZSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuX2ltYWdlcyA9IG5fZGlzdGluY3QoaW1hZ2VfaWQpLA0KICAgIEIxMSA9IGlmIChuX2ltYWdlcyA+IDEpIG1lYW4oQjExLCBuYS5ybSA9IFRSVUUpIGVsc2UgZmlyc3QoQjExKSwNCiAgICBCMiAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEIyLCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEIyKSwNCiAgICBCMyAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEIzLCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEIzKSwNCiAgICBCNCAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEI0LCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEI0KSwNCiAgICBCOCAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEI4LCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEI4KSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkgDQoNCiMgQ2FsY3VsYXRlIGhvdyBtYW55IGRpZmZlcmVudCBkYXlzIGZvciBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQpuX2RheXMgPC0gYmFuZF9zdW1tYXJ5ICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCkgJT4lDQogIHN1bW1hcmlzZShuX2RheXMgPSBuX2Rpc3RpbmN0KGRhdGUpKQ0KDQojIEpvaW4gYmFjayB0byBvcmlnaW5hbCBkYXRhDQpkYXRhX1JTX1MyX2JhbmRzX3VwZGF0ZWQgPC0gZGF0YV9SU19TMl9iYW5kcyAlPiUNCiAgIyBSZW1vdmUgb2xkIGJhbmQgdmFsdWVzDQogIHNlbGVjdCgtQjExLCAtQjIsIC1CMywgLUI0LCAtQjgpICU+JQ0KICAjIEpvaW4gYmFuZF9zdW1tYXJ5DQogIGxlZnRfam9pbihiYW5kX3N1bW1hcnksIGJ5ID0gYygiUGxvdE9ic2VydmF0aW9uSUQiLCAiZGF0ZSIpKSAlPiUNCiAgIyBLZWVwIG9uZSByb3cgcGVyIGdyb3VwDQogIGRpc3RpbmN0KFBsb3RPYnNlcnZhdGlvbklELCBkYXRlLCAua2VlcF9hbGwgPSBUUlVFKSAlPiUNCiAgIyBSZW1vdmUgdW53YW50ZWQgY29sdW1ucw0KICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtaW1hZ2VfaWQsIC0uZ2VvLCAtdGltZV91dGMsIC10aW1lc3RhbXApICU+JQ0KICAjIEpvaW4NCiAgbGVmdF9qb2luKG5fZGF5cykNCmBgYA0KDQojIENhbGN1bGF0ZSBpbmRpY2VzDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQojIENhbGN1bGF0ZSBpbmRpY2VzDQpkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgPC0gZGF0YV9SU19TMl9iYW5kc191cGRhdGVkICU+JQ0KICAjIFNldCBQbG90T2JzZXJ2YXRpb25JRCBhcyBmYWN0b3INCiAgbXV0YXRlKFBsb3RPYnNlcnZhdGlvbklEID0gZmFjdG9yKFBsb3RPYnNlcnZhdGlvbklEKSkgJT4lDQogICMgUmVuYW1lIHRoZSBiYW5kcw0KICByZW5hbWUoYmx1ZSA9IEIyLCBncmVlbiA9IEIzLCByZWQgPSBCNCwgTklSID0gQjgsIFNXSVIgPSBCMTEpICU+JQ0KICAjIFNjYWxlIHRoZSBiYW5kcw0KICBtdXRhdGUoYmx1ZSA9IGJsdWUgLyAxMDAwMCwgZ3JlZW4gPSBncmVlbiAvIDEwMDAwLCByZWQgPSByZWQgLyAxMDAwMCwNCiAgICAgICAgIE5JUiA9IE5JUiAvIDEwMDAwLCBTV0lSID0gU1dJUiAvIDEwMDAwKSAlPiUNCiAgIyBDcmVhdGUgY29sdW1uIHRoYXQgY29tYmluZXMgdGhlIGRheSBvZiB0aGUgbW9udGggYW5kIHRoZSB0aW1lDQogIG11dGF0ZSgNCiAgICBkYXRlID0gYXMuUE9TSVhjdChkYXRlKSwNCiAgICAjIE5vcm1hbGl6ZSB0aGUgZGF0ZXMgdG8gYSBmaXhlZCB5ZWFyICgyMDAwKQ0KICAgICMgc28gdGhhdCBzZWFzb25hbCBwYXR0ZXJucyBhY3Jvc3MgZGlmZmVyZW50IHllYXJzIGNhbiBiZSBjb21wYXJlZCB2aXN1YWxseQ0KICAgIGRheV9tb250aCA9IGFzLlBPU0lYY3QoZm9ybWF0KGRhdGUsICIyMDAwLSVtLSVkIikpKSAlPiUNCiAgIyBDcmVhdGUgY29sdW1uIHdpdGggRE9ZDQogIG11dGF0ZShET1kgPSB5ZGF5KGRhdGUpKSAlPiUNCiAgIyBDYWxjdWxhdGUgTkRWSQ0KICBtdXRhdGUoTkRWSSA9IChOSVIgLSByZWQpIC8gKE5JUiArIHJlZCksDQogICAgICAgICBFVkkgPSAoTklSIC0gcmVkKSAqIDIuNSAvIChOSVIgKyA2ICogcmVkIC0gNy41ICogYmx1ZSArIDEpLA0KICAgICAgICAgU0FWSSA9IChOSVIgLSByZWQpICogMS41IC8gKE5JUiArIHJlZCArIDAuNSksDQogICAgICAgICBORE1JID0gKE5JUiAtIFNXSVIpIC8gKE5JUiArIFNXSVIpLA0KICAgICAgICAgTkRXSSA9IChncmVlbiAtIE5JUikgLyAoZ3JlZW4gKyBOSVIpKSAlPiUNCiAgIyBTZXR0aW5nIHZhbHVlcyBvZiBpbmRpY2VzIG91dHNpZGUgZXhwZWN0ZWQgcmFuZ2VzIChlcnJvcnMpIHRvIE5BDQogIG11dGF0ZShFVkkgPSBpZl9lbHNlKEVWSSA+IDEgfCBFVkkgPCAtMSwgTkEsIEVWSSkpICMgMTIxNjYgdmFsdWVzIG9mIEVWSSBhcyBOQQ0KYGBgDQoNClNhdmU6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpzYXZlKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcywgZmlsZSA9ICJvYmplY3RzL2RhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcy5SZGF0YSIpDQpgYGANCg0KUGxvdCBuX2RheXRpbWU6DQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCkgJT4lDQogIHN1bW1hcmlzZShuX2RheXMgPSBmaXJzdChuX2RheXMpKSAlPiUgdW5ncm91cCgpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBuX2RheXMpKSArIGdlb21faGlzdG9ncmFtKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyBDb21wdXRlIHBoZW5vbG9naWNhbCBtZXRyaWNzIGZyb20gbW9kZWxzIGZpdHRlZCB0byB0aW1lIHNlcmllcyBkYXRhDQoNCiMjIEZ1bmN0aW9uDQoNCiMjIEhFUkUgKGFscmVhZHkgZG9uZSBpbiBMYW5kc2F0KTogTW9kaWZ5IGZ1bmN0aW9uIGFuZCByZXJ1biBldmVyeXRoaW5nIGZyb20gaGVyZSAoVEJEKQ0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBNb2RpZnkgdGhlc2UgcGFydHMgaW4gdGhlIGZ1bmN0aW9uIGJlbG93IHRvIGVuc3VyZSB0aGF0DQojIHNvc190aHJlc2hvbGQgPCBwb3MgYW5kIGVvc190aHJlc2hvbGQgPiBwb3MNCnNvc190aHJlc2hvbGQgPC0gaWYgKCFpcy5uYSh1X3NvcykgJiYgIWlzLm5hKHBvcykpIHsNCiAgY2FuZGlkYXRlcyA8LSB4W3doaWNoKHByZWQgPj0gdV9zb3MgJiB4IDw9IHBvcyldDQogIGlmIChsZW5ndGgoY2FuZGlkYXRlcykgPiAwKSBjYW5kaWRhdGVzWzFdIGVsc2UgTkFfcmVhbF8NCn0gZWxzZSBOQV9yZWFsXw0KDQplb3NfdGhyZXNob2xkIDwtIGlmICghaXMubmEodV9lb3MpKSB7DQogIHhbcmV2KHdoaWNoKHggPiBwb3MgJiBwcmVkID49IHVfZW9zKSlbMV1dDQp9IGVsc2UgTkFfcmVhbF8NCmBgYA0KDQpVc2luZyBHQU1zLCByZXdlaWdodGluZyBhbmQgMyBpdGVyYXRpb25zLg0KDQpVc2luZyBib3RoIGEgY2hhbmdlIGRldGVjdGlvbiBtZXRob2QgKG1heGltdW0gc2xvcGUpIGFuZCBhIHRocmVzaG9sZCBtZXRob2QgKDUwJSBhbXBsaXR1ZGUpIHRvIGNhbGN1bGF0ZSBzb3MgYW5kIGVvcy4NCg0KQXBwcm9hY2ggc2ltaWxhciB0byBodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmphZy4yMDIwLjEwMjE3MiBmb3IgR0FNIGZpdHRpbmcgYW5kIGNoYW5nZSBkZXRlY3Rpb24gbWV0aG9kLCBhbmQgdG8gaHR0cHM6Ly93d3cubWRwaS5jb20vMjA3Mi00MjkyLzEyLzIyLzM3MzggZm90IHRocmVzaG9sZCBtZXRob2QuDQoNCkRlZmluZSBmdW5jdGlvbiB0byBjb21wdXRlIHBoZW5vbG9neSBtZXRyaWNzIHVzaW5nIEdBTSBmaXQgYW5kIE5EVkkgLyBFVkkgLyBTQVZJOg0KDQpgYGB7cn0NCmNvbXB1dGVfbWV0cmljc19tb2RlbHMgPC0gZnVuY3Rpb24oZGYsIGluZGV4X2NvbHMgPSBjKCJORFZJIiwgIkVWSSIsICJTQVZJIikpIHsNCiAgc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsNCiAgICBsaWJyYXJ5KG1nY3YpDQogICAgbGlicmFyeShubG1lKQ0KICAgIH0pDQogIA0KICBwbGFuKG11bHRpc2Vzc2lvbikgICMgU2V0IHVwIHBhcmFsbGVsIHByb2Nlc3NpbmcNCiAgDQogICMgQ3JlYXRlIGEgbGlzdCBvZiBpbmRleC1zcGVjaWZpYyBkYXRhIGZyYW1lcw0KICBpbmRleF9kZnMgPC0gbGFwcGx5KGluZGV4X2NvbHMsIGZ1bmN0aW9uKGluZGV4X2NvbCkgew0KICAgIGxpc3QoaW5kZXhfY29sID0gaW5kZXhfY29sLCBkZiA9IGRmICU+JQ0KICAgICAgICAgICBzZWxlY3QoRE9ZLCBQbG90T2JzZXJ2YXRpb25JRCwgYWxsX29mKGluZGV4X2NvbCkpKQ0KICAgIH0pDQogIA0KICAjIERlZmluZSB0aGUgcHJvY2Vzc2luZyBmdW5jdGlvbiBmb3IgZWFjaCBpbmRleA0KICBwcm9jZXNzX2luZGV4IDwtIGZ1bmN0aW9uKGluZGV4X2RhdGEpIHsNCiAgICBpbmRleF9jb2wgPC0gaW5kZXhfZGF0YSRpbmRleF9jb2wNCiAgICBkZl9pbmRleCA8LSBpbmRleF9kYXRhJGRmICU+JQ0KICAgICAgZmlsdGVyKCFpcy5uYSguZGF0YVtbaW5kZXhfY29sXV0pKSAlPiUNCiAgICAgIGFycmFuZ2UoRE9ZKQ0KICAgIA0KICAgIHBsb3RfaWQgPC0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKQ0KDQogICAgaWYgKG5yb3coZGZfaW5kZXgpIDwgMTApIHsNCiAgICAgIG1lc3NhZ2UoIiAgU2tpcHBlZDogaW5zdWZmaWNpZW50IGRhdGEgKDwgMTAgcm93cykiKQ0KICAgICAgcmV0dXJuKHRpYmJsZShQbG90T2JzZXJ2YXRpb25JRCA9IHBsb3RfaWQsIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgICAgICAgICAgICAgICBzb3Nfc2xvcGUgPSBOQV9yZWFsXywgc29zX3RocmVzaG9sZCA9IE5BX3JlYWxfLA0KICAgICAgICAgICAgICAgICAgICBwb3MgPSBOQV9yZWFsXywgZW9zX3Nsb3BlID0gTkFfcmVhbF8sIA0KICAgICAgICAgICAgICAgICAgICBlb3NfdGhyZXNob2xkID0gTkFfcmVhbF8sIGF1Y19zbG9wZSA9IE5BX3JlYWxfLA0KICAgICAgICAgICAgICAgICAgICBhdWNfdGhyZXNob2xkID0gTkFfcmVhbF8sIFZtYXggPSBOQV9yZWFsXywNCiAgICAgICAgICAgICAgICAgICAgRE9ZID0gZGZfaW5kZXgkRE9ZLCB2YWx1ZSA9IE5BX3JlYWxfKSkNCiAgICB9DQogICAgDQogICAgIyBSZXBsYWNlIGVhcmx5L2xhdGUgRE9ZIHZhbHVlcw0KICAgIGJhc2VfdmFsdWVfZWFybHkgPC0gbWVhbihkZl9pbmRleCAlPiUgZmlsdGVyKERPWSA8PSA1MCkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB1bGwoaW5kZXhfY29sKSwgbmEucm0gPSBUUlVFKQ0KICAgIGJhc2VfdmFsdWVfbGF0ZSAgPC0gbWVhbihkZl9pbmRleCAlPiUgZmlsdGVyKERPWSA+PSAzMTUpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdWxsKGluZGV4X2NvbCksIG5hLnJtID0gVFJVRSkNCg0KICAgIGRmX2luZGV4IDwtIGRmX2luZGV4ICU+JQ0KICAgICAgbXV0YXRlKCEhaW5kZXhfY29sIDo9IGNhc2Vfd2hlbigNCiAgICAgICAgRE9ZIDw9IDUwIH4gYmFzZV92YWx1ZV9lYXJseSwNCiAgICAgICAgRE9ZID49IDMxNSB+IGJhc2VfdmFsdWVfbGF0ZSwNCiAgICAgICAgVFJVRSB+IC5kYXRhW1tpbmRleF9jb2xdXQ0KICAgICAgKSkNCg0KICAgIHggPC0gZGZfaW5kZXgkRE9ZDQogICAgeSA8LSBkZl9pbmRleFtbaW5kZXhfY29sXV0NCiAgICB3ZWlnaHRzIDwtIHJlcCgxLCBsZW5ndGgoeSkpDQogICAgDQogICAgIyBHQU0gZml0DQogICAgcHJlZCA8LSBOVUxMDQogICAgZm9yIChpIGluIDE6Mykgew0KICAgICAgZ2FtX2ZpdCA8LSB0cnlDYXRjaCh7DQogICAgICAgIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpLHdlaWdodHMgPSB3ZWlnaHRzKQ0KICAgICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICAgICAgICBtZXNzYWdlKCIgIEdBTSBmaXR0aW5nIGZhaWxlZCBmb3IgIiwgcGxvdF9pZCwgIiAtICIsIGluZGV4X2NvbCwgIjogIiwgDQogICAgICAgICAgICAgICAgICBlJG1lc3NhZ2UpDQogICAgICAgICAgcmV0dXJuKE5VTEwpDQogICAgICAgICAgfSkNCiAgICAgIGlmIChpcy5udWxsKGdhbV9maXQpKSB7DQogICAgICAgIHJldHVybih0aWJibGUoDQogICAgICAgICAgUGxvdE9ic2VydmF0aW9uSUQgPSBwbG90X2lkLA0KICAgICAgICAgIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgICAgIHNvc19zbG9wZSA9IE5BX3JlYWxfLA0KICAgICAgICAgIHNvc190aHJlc2hvbGQgPSBOQV9yZWFsXywNCiAgICAgICAgICBwb3MgPSBOQV9yZWFsXywNCiAgICAgICAgICBlb3Nfc2xvcGUgPSBOQV9yZWFsXywgDQogICAgICAgICAgZW9zX3RocmVzaG9sZCA9IE5BX3JlYWxfLCANCiAgICAgICAgICBhdWNfc2xvcGUgPSBOQV9yZWFsXywNCiAgICAgICAgICBhdWNfdGhyZXNob2xkID0gTkFfcmVhbF8sIA0KICAgICAgICAgIFZtaW5fcHJlID0gTkFfcmVhbF8sIA0KICAgICAgICAgIFZtaW5fcG9zdCA9IE5BX3JlYWxfLA0KICAgICAgICAgIFZtYXggPSBOQV9yZWFsXywgDQogICAgICAgICAgdV9zb3MgPSBOQV9yZWFsXywgDQogICAgICAgICAgdV9lb3MgPSBOQV9yZWFsXywNCiAgICAgICAgICBET1kgPSBkZl9pbmRleCRET1ksDQogICAgICAgICAgdmFsdWUgPSBOQV9yZWFsXykpDQogICAgICAgIH0NCiAgICAgIA0KICAgICAgcHJlZCA8LSB0cnlDYXRjaCh7DQogICAgICAgIHByZWRpY3QoZ2FtX2ZpdCwgbmV3ZGF0YSA9IHRpYmJsZSh4ID0geCkpDQogICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgew0KICAgICAgICAgIG1lc3NhZ2UoIlByZWRpY3Rpb24gZmFpbGVkIGZvciAiLCBwbG90X2lkLCAiIC0gIiwgaW5kZXhfY29sLCAiOiAiLA0KICAgICAgICAgICAgICAgICAgZSRtZXNzYWdlKQ0KICAgICAgICAgIHJldHVybihyZXAoTkFfcmVhbF8sIGxlbmd0aCh4KSkpDQogICAgICAgICAgfSkNCiAgICAgIA0KICAgICAgaWR4X2JldHdlZW4gPC0gd2hpY2goeCA+IDUwICYgeCA8IDMxNSAmICFpcy5uYShwcmVkKSAmIHByZWQgIT0gMCkNCiAgICAgIHdlaWdodHMgPC0gcmVwKDEsIGxlbmd0aCh5KSkNCiAgICAgIHdlaWdodHNbaWR4X2JldHdlZW5dIDwtICh5W2lkeF9iZXR3ZWVuXSAvIChwcmVkW2lkeF9iZXR3ZWVuXSArIDFlLTYpKV40DQogICAgICB3ZWlnaHRzW3dlaWdodHMgPiAxIHwgaXMubmEod2VpZ2h0cyldIDwtIDENCiAgICAgIH0NCiAgICANCiAgICAjIENvbXB1dGUgbWV0cmljcw0KICAgIHNsb3BlIDwtIGMoTkEsIGRpZmYocHJlZCkpDQogICAgaWR4IDwtIHdoaWNoKHggPj0gNTAgJiB4IDw9IDMxNSkNCiAgICBwb3MgPC0gaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1heChwcmVkW2lkeF0pXSBlbHNlIE5BX3JlYWxfDQoNCiAgICBzb3Nfc2xvcGUgPC0gaWYgKCFpcy5uYShwb3MpKSB7DQogICAgICBpZHggPC0gd2hpY2goeCA8IHBvcykNCiAgICAgIGlmIChsZW5ndGgoaWR4KSA+IDApIHhbaWR4XVt3aGljaC5tYXgoc2xvcGVbaWR4XSldIGVsc2UgTkFfcmVhbF8NCiAgICB9IGVsc2UgTkFfcmVhbF8NCg0KICAgIGVvc19zbG9wZSA8LSBpZiAoIWlzLm5hKHBvcykpIHsNCiAgICAgIGlkeCA8LSB3aGljaCh4ID4gcG9zKQ0KICAgICAgaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1pbihzbG9wZVtpZHhdKV0gZWxzZSBOQV9yZWFsXw0KICAgIH0gZWxzZSBOQV9yZWFsXw0KDQogICAgaW50ZWdyYXRpb25faWR4X3Nsb3BlIDwtIHdoaWNoKHggPj0gc29zX3Nsb3BlICYgeCA8PSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlb3Nfc2xvcGUgJiAhaXMubmEocHJlZCkpDQogICAgYXVjX3Nsb3BlIDwtIGlmIChsZW5ndGgoaW50ZWdyYXRpb25faWR4X3Nsb3BlKSA+IDEpIHsNCiAgICAgIHN1bShkaWZmKHhbaW50ZWdyYXRpb25faWR4X3Nsb3BlXSkgKiANCiAgICAgICAgICAgIHpvbzo6cm9sbG1lYW4ocHJlZFtpbnRlZ3JhdGlvbl9pZHhfc2xvcGVdLCAyKSkNCiAgICAgIH0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgICMgVm1pbiBhbnRlcyB5IGRlc3B1w6lzIGRlbCBwaWNvDQogICAgVm1pbl9wcmUgPC0gaWYgKCFpcy5uYShwb3MpKSBtaW4ocHJlZFt4IDw9IHBvc10sIG5hLnJtID0gVFJVRSllbHNlIE5BX3JlYWxfDQogICAgVm1pbl9wb3N0IDwtIGlmICghaXMubmEocG9zKSkgbWluKHByZWRbeCA+PSBwb3NdLCBuYS5ybSA9IFRSVUUpIGVsc2UgTkFfcmVhbF8NCiAgICBWbWF4IDwtIG1heChwcmVkLCBuYS5ybSA9IFRSVUUpDQogICAgDQogICAgIyBVbWJyYWxlcyByZWxhdGl2b3MNCiAgICBwIDwtIDAuNQ0KICAgIHVfc29zIDwtIGlmICghaXMubmEoVm1pbl9wcmUpKSBWbWluX3ByZSArIHAgKiAoVm1heCAtIFZtaW5fcHJlKSBlbHNlIE5BX3JlYWxfDQogICAgdV9lb3MgPC0gaWYgKCFpcy5uYShWbWluX3Bvc3QpKSBWbWluX3Bvc3QgKyBwICogKFZtYXggLSBWbWluX3Bvc3QpIGVsc2UgTkFfcmVhbF8NCiAgICANCiAgICAjIERPWSBkb25kZSBzZSBjcnV6YW4gbG9zIHVtYnJhbGVzDQogICAgc29zX3RocmVzaG9sZCA8LSBpZiAoIWlzLm5hKHVfc29zKSkgeFt3aGljaChwcmVkID49IHVfc29zKVsxXV0gZWxzZSBOQV9yZWFsXw0KICAgIGVvc190aHJlc2hvbGQgPC0gaWYgKCFpcy5uYSh1X2VvcykpIHhbcmV2KHdoaWNoKHByZWQgPj0gdV9lb3MpKVsxXV0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgIGludGVncmF0aW9uX2lkeF90aHJlc2hvbGQgPC0gd2hpY2goeCA+PSBzb3NfdGhyZXNob2xkICYgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPD0gZW9zX3RocmVzaG9sZCAmICFpcy5uYShwcmVkKSkNCiAgICBhdWNfdGhyZXNob2xkIDwtIGlmIChsZW5ndGgoaW50ZWdyYXRpb25faWR4X3RocmVzaG9sZCkgPiAxKSB7DQogICAgICBzdW0oZGlmZih4W2ludGVncmF0aW9uX2lkeF90aHJlc2hvbGRdKSAqIA0KICAgICAgICAgICAgem9vOjpyb2xsbWVhbihwcmVkW2ludGVncmF0aW9uX2lkeF90aHJlc2hvbGRdLCAyKSkNCiAgICAgIH0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgICMgMS4gUHJlZGljY2lvbmVzIHBvciBET1kNCiAgICBmaXRzX2RmIDwtIHRpYmJsZSgNCiAgICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICAgIERPWSA9IHgsDQogICAgICB2YWx1ZSA9IHByZWQsDQogICAgICBpbmRleCA9IGluZGV4X2NvbA0KICAgICAgKQ0KICAgIA0KICAgICMgMi4gTcOpdHJpY2FzIHJlc3VtZW4NCiAgICBtZXRyaWNzX2RmIDwtIHRpYmJsZSgNCiAgICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICAgIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgc29zX3Nsb3BlID0gc29zX3Nsb3BlLA0KICAgICAgc29zX3RocmVzaG9sZCA9IHNvc190aHJlc2hvbGQsDQogICAgICBwb3MgPSBwb3MsDQogICAgICBlb3Nfc2xvcGUgPSBlb3Nfc2xvcGUsDQogICAgICBlb3NfdGhyZXNob2xkID0gZW9zX3RocmVzaG9sZCwNCiAgICAgIGF1Y19zbG9wZSA9IGF1Y19zbG9wZSwNCiAgICAgIGF1Y190aHJlc2hvbGQgPSBhdWNfdGhyZXNob2xkLA0KICAgICAgVm1pbl9wcmUgPSBWbWluX3ByZSwNCiAgICAgIFZtaW5fcG9zdCA9IFZtaW5fcG9zdCwNCiAgICAgIFZtYXggPSBWbWF4LA0KICAgICAgdV9zb3MgPSB1X3NvcywNCiAgICAgIHVfZW9zID0gdV9lb3MNCiAgICAgICkNCiAgICANCiAgICAjIDMuIFVuaXIgcG9yIFBsb3RPYnNlcnZhdGlvbklELCBpbmRleA0KICAgIGZpbmFsX2RmIDwtIGxlZnRfam9pbihmaXRzX2RmLCBtZXRyaWNzX2RmLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJQbG90T2JzZXJ2YXRpb25JRCIsICJpbmRleCIpKQ0KICB9DQogIA0KICAjIFJ1biBpbiBwYXJhbGxlbA0KICByZXN1bHRzIDwtIGZ1dHVyZV9tYXAoaW5kZXhfZGZzLCBwcm9jZXNzX2luZGV4LCAucHJvZ3Jlc3MgPSBUUlVFKQ0KICByZXN1bHRzIDwtIHB1cnJyOjpjb21wYWN0KHJlc3VsdHMpICAjIHJlbW92ZXMgTlVMTHMNCiAgaWYgKGxlbmd0aChyZXN1bHRzKSA9PSAwKSByZXR1cm4odGliYmxlKCkpICAjIG9yIHJldHVybihOVUxMKQ0KICBiaW5kX3Jvd3MocmVzdWx0cykNCn0NCmBgYA0KDQojIyBDYWxjdWxhdGlvbg0KDQpBcHBseSB0aGUgZnVuY3Rpb24gd2l0aCBiYXRjaCBwcm9jZXNzaW5nDQoNCmBgYHtyIGV2YWw9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpwbGFuKG11bHRpc2Vzc2lvbiwgd29ya2VycyA9IGF2YWlsYWJsZUNvcmVzKCkgLSAxKQ0KDQppZHMgPC0gdW5pcXVlKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyRQbG90T2JzZXJ2YXRpb25JRCkNCmJhdGNoZXMgPC0gc3BsaXQoaWRzLCBjZWlsaW5nKHNlcV9hbG9uZyhpZHMpIC8gNTApKSAgIyBiYXRjaGVzIG9mIDUwDQoNCnN0YXJ0X3RvdGFsIDwtIFN5cy50aW1lKCkNCg0KR0FNX2RhdGEgPC0gbWFwX2RmcihzZXFfYWxvbmcoYmF0Y2hlcyksIGZ1bmN0aW9uKGkpIHsNCiAgYmF0Y2hfaWRzIDwtIGJhdGNoZXNbW2ldXQ0KICB0b3RhbF9iYXRjaGVzIDwtIGxlbmd0aChiYXRjaGVzKQ0KICBiYXRjaF9maWxlIDwtIGZpbGUucGF0aCgib2JqZWN0cy9HQU1fYmF0Y2hlc19TMiIsIHBhc3RlMCgiYmF0Y2hfIiwgaSwgIi5yZHMiKSkNCg0KICBpZiAoZmlsZS5leGlzdHMoYmF0Y2hfZmlsZSkpIHsNCiAgICBtZXNzYWdlKCLinIUgQmF0Y2ggICIsIGksICIgb2YgIiwgdG90YWxfYmF0Y2hlcywgDQogICAgICAgICAgICAiIGFscmVhZHkgcHJvY2Vzc2VkLiBMb2FkaW5nIGZyb20gZmlsZS4iKQ0KICAgIHJldHVybihyZWFkUkRTKGJhdGNoX2ZpbGUpKQ0KICB9DQoNCiAgbWVzc2FnZSgi8J+UhCBQcm9jZXNzaW5nIGJhdGNoICAiLCBpLCAiIG9mICIsIHRvdGFsX2JhdGNoZXMsICIgd2l0aCAiLA0KICAgICAgICAgIGxlbmd0aChiYXRjaF9pZHMpLCAiIElEcy4uLiIpDQoNCiAgc3RhcnRfYmF0Y2ggPC0gU3lzLnRpbWUoKQ0KDQogIHJlc3VsdCA8LSBkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEICVpbiUgYmF0Y2hfaWRzKSAlPiUNCiAgICBncm91cF9zcGxpdChQbG90T2JzZXJ2YXRpb25JRCkgJT4lDQogICAgc2V0X25hbWVzKG1hcF9jaHIoLiwgfiBhcy5jaGFyYWN0ZXIodW5pcXVlKC54JFBsb3RPYnNlcnZhdGlvbklEKSkpKSAlPiUNCiAgICBmdXR1cmVfbWFwX2Rmcih+IGNvbXB1dGVfbWV0cmljc19tb2RlbHMoZGYgPSAuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleF9jb2xzID0gYygiTkRWSSIsICJFVkkiLCAiU0FWSSIpKSwNCiAgICAgICAgICAgICAgICAgICAucHJvZ3Jlc3MgPSBUUlVFKQ0KDQogIGVuZF9iYXRjaCA8LSBTeXMudGltZSgpDQogIGR1cmF0aW9uIDwtIHJvdW5kKGRpZmZ0aW1lKGVuZF9iYXRjaCwgc3RhcnRfYmF0Y2gsIHVuaXRzID0gIm1pbnMiKSwgMikNCiAgbWVzc2FnZSgi4o+x77iPIEJhdGNoIHRpbWUgIiwgaSwgIjogIiwgZHVyYXRpb24sICIgbWludXRlcyIpDQoNCiAgbWVzc2FnZSgi8J+SviBTYXZpbmcgYmF0Y2ggIiwgaSwgIiB0byBmaWxlLi4uIikNCiAgc2F2ZVJEUyhyZXN1bHQsIGJhdGNoX2ZpbGUpDQogIG1lc3NhZ2UoIuKchSBCYXRjaCAiLCBpLCAiIHNhdmVkLiIpIA0KDQogIHJlc3VsdA0KfSkNCg0KZW5kX3RvdGFsIDwtIFN5cy50aW1lKCkNCnRvdGFsX3RpbWUgPC0gcm91bmQoZGlmZnRpbWUoZW5kX3RvdGFsLCBzdGFydF90b3RhbCwgdW5pdHMgPSAibWlucyIpLCAyKQ0KbWVzc2FnZSgi4o+x77iPIFRvdGFsIHRpbWU6ICIsIHRvdGFsX3RpbWUsICIgbWludXRlcyIpDQpgYGANCg0KYGBge3J9DQpwbGFuKHNlcXVlbnRpYWwpDQpgYGANCg0KIyMgU2F2ZQ0KDQpMb29rOg0KDQpgYGB7cn0NCkdBTV9kYXRhDQpgYGANCg0KU2F2ZSBhcyBhbiBvYmplY3Q6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpzYXZlKEdBTV9kYXRhLCBmaWxlID0gIm9iamVjdHMvR0FNX2RhdGFfUzIuUmRhdGEiKQ0KYGBgDQoNCiMjIEV4dHJhY3QgYXZlcmFnZSB2YWx1ZXMgb2YgaW5kaWNlcyBwZXIgbW9udGgNCg0KRXh0cmFjdCBhdmVyYWdlIHZhbHVlcyBvZiBpbmRpY2VzIHBlciBtb250aCBhbmQgQVVDIGJldHdlZW4gTWFyY2ggYW5kIE9jdG9iZXIgDQoNCmBgYHtyfQ0KZXh0cmFjdF9tb250aGx5X2F2Z19pbmRpY2VzIDwtIGZ1bmN0aW9uKA0KICBHQU1fZGF0YSwgDQogIG1vbnRobHlfZG95cyA9IGxpc3QoIjAxIiA9IDE6MzEsICIwMiIgPSAzMjo1OSwgIjAzIiA9IDYwOjkwLCAiMDQiID0gOTE6MTIwLCANCiAgICAgICAgICAgICAgICAgICAgICAiMDUiID0gMTIxOjE1MSwgIjA2IiA9IDE1MjoxODEsICIwNyIgPSAxODI6MjEyLCANCiAgICAgICAgICAgICAgICAgICAgICAiMDgiID0gMjEzOjI0MywgIjA5IiA9IDI0NDoyNzMsICIxMCIgPSAyNzQ6MzA0LA0KICAgICAgICAgICAgICAgICAgICAgICIxMSIgPSAzMDU6MzM0LCAiMTIiID0gMzM1OjM2NSkpIHsNCiAgDQogIG1vbnRobHlfZGYgPC0gR0FNX2RhdGEgJT4lDQogICAgbXV0YXRlKG1vbnRoID0gcHVycnI6Om1hcF9jaHIoRE9ZLCBmdW5jdGlvbihkb3kpIHsNCiAgICAgIG1vbnRoX25hbWUgPC0gbmFtZXMobW9udGhseV9kb3lzKVtzYXBwbHkobW9udGhseV9kb3lzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24ocikgZG95ICVpbiUgcildDQogICAgICBpZiAobGVuZ3RoKG1vbnRoX25hbWUpID4gMCkgbW9udGhfbmFtZSBlbHNlIE5BX2NoYXJhY3Rlcl8NCiAgICB9KSkgJT4lDQogICAgZHBseXI6OmZpbHRlcighaXMubmEobW9udGgpKSAlPiUNCiAgICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgsIG1vbnRoKSAlPiUNCiAgICBzdW1tYXJpc2UoYXZnX3ZhbHVlID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogICAgbXV0YXRlKGF2Z192YWx1ZSA9IGlmZWxzZShpcy5pbmZpbml0ZShhdmdfdmFsdWUpLCBOQSwgYXZnX3ZhbHVlKSkgJT4lDQogICAgYXJyYW5nZShQbG90T2JzZXJ2YXRpb25JRCwgbWF0Y2gobW9udGgsIG5hbWVzKG1vbnRobHlfZG95cykpKSAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbW9udGgsIHZhbHVlc19mcm9tID0gYXZnX3ZhbHVlLCANCiAgICAgICAgICAgICAgICBuYW1lc19wcmVmaXggPSAiYXZnX3ZhbHVlXyIpDQogIA0KICAjIENhbGN1bGFyIEFVQyBlbnRyZSBtYXJ6byB5IG9jdHVicmUgdXNhbmRvIHJlZ2xhIGRlbCB0cmFwZWNpbw0KICBtb250aHNfYXVjIDwtIGMoIjAzIiwgIjA0IiwgIjA1IiwgIjA2IiwgIjA3IiwgIjA4IiwgIjA5IiwgIjEwIikNCiAgIyBET1kgYXByb3hpbWFkbyBkZWwgY2VudHJvIGRlIGNhZGEgbWVzDQogIGRveV9taWRwb2ludHMgPC0gYyg3NSwgMTA1LCAxMzUsIDE2NSwgMTk1LCAyMjUsIDI1NSwgMjg1KSAgDQogIA0KICBtb250aGx5X2RmIDwtIG1vbnRobHlfZGYgJT4lDQogICAgcm93d2lzZSgpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIGF1Y19tYXJfb2N0ID0gew0KICAgICAgICB2YWx1ZXMgPC0gY19hY3Jvc3MoYWxsX29mKHBhc3RlMCgiYXZnX3ZhbHVlXyIsIG1vbnRoc19hdWMpKSkNCiAgICAgICAgaWYgKGFueShpcy5uYSh2YWx1ZXMpKSkgTkFfcmVhbF8gZWxzZSBzdW0oZGlmZihkb3lfbWlkcG9pbnRzKSAqDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgem9vOjpyb2xsbWVhbih2YWx1ZXMsIDIpKQ0KICAgICAgfQ0KICAgICkgJT4lDQogICAgdW5ncm91cCgpDQogIA0KICByZXR1cm4obW9udGhseV9kZikNCn0NCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KbW9udGhseV9hdmdfaW5kaWNlcyA8LSBleHRyYWN0X21vbnRobHlfYXZnX2luZGljZXMoR0FNX2RhdGEpDQpgYGANCg0KU2F2ZSBhcyBhbiBvYmplY3Q6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpzYXZlKG1vbnRobHlfYXZnX2luZGljZXMsIGZpbGUgPSAib2JqZWN0cy9tb250aGx5X2F2Z19pbmRpY2VzX1MyLlJkYXRhIikNCmBgYA0KDQojIyBBc3Nlc3MgdGltZSBzZXJpZXMgcXVhbGl0eQ0KDQpGb3IgdGhlIHRpbWUgc2VyaWVzIHRvIGJlIGFjY2VwdGFibGUsIGl0IHNob3VsZCBoYXZlIGEgcmVhc29uYWJsZSBudW1iZXIgb2YgdGltZSBwb2ludHMsIGFuZCB0aGVzZSBwb2ludHMgc2hvdWxkIGJlIGRpc3RyaWJ1dGVkIGFsb25nIGFsbW9zdCBhbGwgbW9udGhzIChjb3VsZCBiZSBvayB0byBtaXNzIHRoZSB3aW50ZXIgbW9udGhzKS4NCg0KSW4gR0FNIGRhdGEsIGNoZWNrIGhvdyBtYW55IHRpbWUgcG9pbnRzIGFyZSB0aGVyZSBmb3IgZWFjaCBQbG90T2JzZXJ2YXRpb25JRCwgaG93IG1hbnkgbW9udGhzLCBhbmQgd2hpY2ggbW9udGhzIGFyZSBtaXNzaW5nLg0KDQpgYGB7cn0NCnRzX3F1YWxpdHkgPC0gR0FNX2RhdGEgJT4lDQogICMgRmlsdGVyIG9ubHkgTkRWSSAoYWxsIGluZGljZXMgd2lsbCBoYXZlIHRoZSBzYW1lIHRpbWUgcG9pbnRzKQ0KICBkcGx5cjo6ZmlsdGVyKGluZGV4ID09ICJORFZJIikgJT4lDQogICMgR2V0IG1vbnRoIGZyb20gRE9ZDQogIG11dGF0ZShtb250aCA9IG1vbnRoKHltZCgiMjAyMC0wMS0wMSIpICsgZGF5cyhET1kgLSAxKSkpICU+JQ0KICAjIEZvciBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQogIGdyb3VwX2J5KFBsb3RPYnNlcnZhdGlvbklEKSAlPiUNCiAgIyBHZXQgdGhlIG51bWJlciBvZiB0aW1lIHBvaW50cyAoZGF5cykgYW5kIHRoZSBudW1iZXIgb2YgbW9udGhzDQogIHN1bW1hcmlzZSgNCiAgICBuX2RheXMgPSBuX2Rpc3RpbmN0KERPWSksDQogICAgbl9tb250aHMgPSBuX2Rpc3RpbmN0KG1vbnRoKSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkgJT4lDQogIGxlZnRfam9pbihHQU1fZGF0YSAlPiUNCiAgICAgICAgICAgICAgIyBGaWx0ZXIgb25seSBORFZJDQogICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoaW5kZXggPT0gIk5EVkkiKSAlPiUNCiAgICAgICAgICAgICAgIyBHZXQgbW9udGggZnJvbSBET1kNCiAgICAgICAgICAgICAgbXV0YXRlKG1vbnRoID0gbW9udGgoeW1kKCIyMDIwLTAxLTAxIikgKyBkYXlzKERPWSAtIDEpKSkgJT4lDQogICAgICAgICAgICAgICMgR2V0IHVuaXF1ZSB2YWx1ZXMgb2YgUGxvdE9ic2VydmF0aW9uSUQgYW5kIG1vbnRoDQogICAgICAgICAgICAgIGRpc3RpbmN0KFBsb3RPYnNlcnZhdGlvbklELCBtb250aCkgJT4lDQogICAgICAgICAgICAgICMgQWRkIDEgYXMgdmFsdWUNCiAgICAgICAgICAgICAgbXV0YXRlKHZhbHVlID0gMSkgJT4lDQogICAgICAgICAgICAgICMgUmVzaGFwZSB0byB3aWRlIGZvcm1hdCBhbmQgYWRkIHplcm9zIHdoZW4gbW9udGggaXMgbWlzc2luZw0KICAgICAgICAgICAgICBwaXZvdF93aWRlcigNCiAgICAgICAgICAgICAgICBuYW1lc19mcm9tID0gbW9udGgsDQogICAgICAgICAgICAgICAgbmFtZXNfcHJlZml4ID0gIm1vbnRoIiwNCiAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHZhbHVlLA0KICAgICAgICAgICAgICAgIHZhbHVlc19maWxsID0gMCksDQogICAgICAgICAgICBieSA9ICJQbG90T2JzZXJ2YXRpb25JRCIpDQpgYGANCg0KSGlzdG9ncmFtcyB0aW1lIHBvaW50cyBhbmQgbiBtb250aHM6DQoNCmBgYHtyfQ0KZ2dwbG90KHRzX3F1YWxpdHksIGFlcyh4ID0gbl9kYXlzKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIsIGZpbGwgPSAid2hpdGUiKSArDQogIHhsYWIoIk51bWJlciBvZiB0aW1lIHBvaW50cyAoZGF5cykgaW4gdGhlIFMyIHRpbWUgc2VyaWVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdncGxvdCh0c19xdWFsaXR5LCBhZXMoeCA9IG5fbW9udGhzKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIsIGZpbGwgPSAid2hpdGUiKSArDQogIHhsYWIoIk51bWJlciBvZiBtb250aHMgaW4gdGhlIFMyIHRpbWUgc2VyaWVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpDb3VudCBob3cgbWFueSBQbG90T2JzZXJ2YXRpb25JRHMgaGF2ZSBtaXNzaW5nIGRhdGEgKHZhbHVlIDApIGZvciBlYWNoIG1vbnRoOg0KDQpgYGB7cn0NCm9ic19taXNzaW5nX21vbnRoIDwtIHRzX3F1YWxpdHkgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3Moc3RhcnRzX3dpdGgoIm1vbnRoIiksIH4gc3VtKC54ID09IDApKSkgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLCBuYW1lc190byA9ICJtb250aCIsIHZhbHVlc190byA9ICJub2JzX21pc3NpbmciKQ0KDQpnZ3Bsb3Qob2JzX21pc3NpbmdfbW9udGggJT4lDQogICAgICAgICBtdXRhdGUobW9udGggPSBmYWN0b3IobW9udGgsIGxldmVscyA9IHBhc3RlMCgibW9udGgiLCAxOjEyKSkpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbm9ic19taXNzaW5nKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICB5bGFiKCJOdW1iZXIgb2YgUGxvdE9ic2VydmF0aW9uSUQgd2l0aCBtaXNzaW5nIGRhdGEiKSArDQogIGdndGl0bGUoIk1pc3NpbmcgZGF0YSBpbiBTMiB0aW1lIHNlcmllcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KQWRkIHF1YWxpdHkgZmxhZzoNCg0KYGBge3J9DQp0c19xdWFsaXR5X2ZsYWcgPC0gdHNfcXVhbGl0eSAlPiUNCiAgcm93d2lzZSgpICU+JQ0KICBtdXRhdGUoDQogICAgIyAgSWYgMiBjb25zZWN1dGl2ZSBtb250aHMgb2YgdGhlIHBlcmlvZCBNYXJjaC1PY3RvYmVyIGFyZSBtaXNzaW5nDQogICAgIyBxdWFsaXR5X2ZsYWcgPSAwDQogICAgcXVhbGl0eV9mbGFnID0gew0KICAgICAgbW9udGhzIDwtIGNfYWNyb3NzKG1vbnRoMzptb250aDEwKQ0KICAgICAgaWYgKGFueShtb250aHNbLWxlbmd0aChtb250aHMpXSA9PSAwICYgbW9udGhzWy0xXSA9PSAwKSkgMCBlbHNlIDENCiAgICB9DQogICkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQoNCmBgYHtyfQ0KdHNfcXVhbGl0eV9mbGFnICU+JSBjb3VudChxdWFsaXR5X2ZsYWcpDQpgYGANCg0KIyMgQm94cGxvdCBjb21wYXJpbmcgbW9tZW50cyBmb3IgZGlmZmVyZW50IGluZGljZXMNCg0KYGBge3J9DQpHQU1fZGF0YSAlPiUgDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgsIHNvc19zbG9wZSwgc29zX3RocmVzaG9sZCwgcG9zLCBlb3Nfc2xvcGUsDQogICAgICAgICBlb3NfdGhyZXNob2xkKSAlPiUgZGlzdGluY3QoKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKHNvc19zbG9wZSwgc29zX3RocmVzaG9sZCwgcG9zLCBlb3Nfc2xvcGUsIGVvc190aHJlc2hvbGQpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibW9tZW50IiwgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lDQogIGdncGxvdChhZXMoeCA9IG1vbWVudCwgeSA9IHZhbHVlLCBmaWxsID0gaW5kZXgpKSArIGdlb21fYm94cGxvdCgpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIGVhY2ggUGxvdE9ic2VydmF0aW9uSUQNCg0KIyMjIFF1YWxpdHkgPSAxDQoNCmBgYHtyfQ0KIyBHZXQgdW5pcXVlIElEcyB3aXRoIHF1YWxpdHlfZmxhZyA9PSAxDQppZHNfcTEgPC0gdHNfcXVhbGl0eV9mbGFnICU+JQ0KICBkcGx5cjo6ZmlsdGVyKHF1YWxpdHlfZmxhZyA9PSAxKSAlPiUNCiAgbXV0YXRlKFBsb3RPYnNlcnZhdGlvbklEID0gZHJvcGxldmVscyhQbG90T2JzZXJ2YXRpb25JRCkpICU+JQ0KICBwdWxsKFBsb3RPYnNlcnZhdGlvbklEKQ0KR0FNX2RhdGFfaWRzX3ExIDwtIEdBTV9kYXRhICU+JQ0KICAjIEpvaW4gdG8gZ2V0IGJpb2dlbyBhbmQgdW5pdA0KICBsZWZ0X2pvaW4oZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGJpb2dlbywgdW5pdCkgJT4lDQogICAgICAgICAgICAgIGRpc3RpbmN0KCkpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IEVVTklTIGluZm8NCiAgIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzICU+JQ0KICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsDQogICAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyKSkgJT4lDQogICMgSm9pbiB0byBnZXQgb3JpZ2luYWwgdmFsdWVzIG9mIGluZGljZXMNCiAgbGVmdF9qb2luKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBET1ksIE5EVkksIEVWSSwgU0FWSSkgJT4lDQogICAgICAgICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gYyhORFZJLCBFVkksIFNBVkkpLCBuYW1lc190byA9ICJpbmRleCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlX29yaWciKSkgJT4lDQogICMgSm9pbiB0byBnZXQgdHNfcXVhbGl0eSBkYXRhDQogIGxlZnRfam9pbih0c19xdWFsaXR5X2ZsYWcgJT4lIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgcXVhbGl0eV9mbGFnKSkgJT4lDQogICMgS2VlcCBvbmx5IHRob3NlIHdpdGggcXVhbGl0eV9mbGFnID09IDENCiAgZHBseXI6OmZpbHRlcihxdWFsaXR5X2ZsYWcgPT0gMSkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBHZXQgdW5pcXVlIFBsb3RPYnNlcnZhdGlvbklEcw0KdW5pcXVlX2lkczEgPC0gaWRzX3ExDQoNCiMgQ3JlYXRlIGFuZCBzdG9yZSBwbG90cyBpbiBhIGxpc3QNCnRzX3Bsb3RzX3ExIDwtIG1hcCh1bmlxdWVfaWRzMSwgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIEdBTV9kYXRhX2lkc19xMSAlPiUNCiAgICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBhcy5jaGFyYWN0ZXIoUGxvdE9ic2VydmF0aW9uSUQpKSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEID09IGlkKSANCiAgDQogICMgRXh0cmFjdCBtZXRhZGF0YSBmb3IgdGl0bGUNCiAgbWV0YWRhdGEgPC0gcGxvdF9kYXRhICU+JQ0KICAgIHNlbGVjdChiaW9nZW8sIHVuaXQsIEVVTklTYV8xLCBFVU5JU2FfMiwgcXVhbGl0eV9mbGFnKSAlPiUNCiAgICBkaXN0aW5jdCgpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwbG90X2RhdGEsYWVzKHggPSBET1ksIHkgPSB2YWx1ZV9vcmlnKSwgYWxwaGEgPSAwLjUpICsNCiAgICBnZW9tX2xpbmUoZGF0YSA9IHBsb3RfZGF0YSwgYWVzKHggPSBET1ksIHkgPSB2YWx1ZSksIA0KICAgICAgICAgICAgICBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHNvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgc29zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHBvcyksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHBvcywgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIGVvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgZW9zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoaW5kZXgpKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgie2lkfSB8IHttZXRhZGF0YSRiaW9nZW99e21ldGFkYXRhJHVuaXR9IHwge21ldGFkYXRhJEVVTklTYV8xfSB8IHttZXRhZGF0YSRFVU5JU2FfMn0gfCBRdWFsaXR5OiB7bWV0YWRhdGEkcXVhbGl0eV9mbGFnfSIpLA0KICAgICAgeCA9ICJEYXkgb2YgWWVhciIsDQogICAgICB5ID0gIkluZGV4IFZhbHVlIg0KICAgICkgKw0KICAgIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KfSkNCg0KIyBOYW1lIHRoZSBsaXN0IGJ5IFBsb3RPYnNlcnZhdGlvbklEDQpuYW1lcyh0c19wbG90c19xMSkgPC0gdW5pcXVlX2lkczENCg0KIyBEaXNwbGF5IHRoZSBmaXJzdCBwbG90DQp0c19wbG90c19xMVsxXQ0KYGBgDQoNClNhdmUgZWFjaCBwbG90IHRvIGEgZmlsZToNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCndhbGsyKHRzX3Bsb3RzX3ExLCBzZXFfYWxvbmcodHNfcGxvdHNfcTEpLCB+IGdnc2F2ZSgNCiAgZmlsZW5hbWUgPSBwYXN0ZTAoIkM6L0FuYWx5c2VzL01PVElWQVRFX3ZhbGlkYXRpb24vb3V0cHV0L2ZpZ3VyZXMvcGhlbm9sb2d5L3RzX3ExX1MyL3RzX3Bsb3RzX3ExIiwgLnksICIuanBlZyIpLA0KICBwbG90ID0gLngsDQogIHdpZHRoID0gOCwNCiAgaGVpZ2h0ID0gNQ0KKSkNCmBgYA0KDQojIyMgUXVhbGl0eSA9IDANCg0KYGBge3J9DQojIEdldCB1bmlxdWUgSURzIHdpdGggcXVhbGl0eV9mbGFnID09IDANCmlkc19xMCA8LSB0c19xdWFsaXR5X2ZsYWcgJT4lDQogIGRwbHlyOjpmaWx0ZXIocXVhbGl0eV9mbGFnID09IDApICU+JQ0KICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBkcm9wbGV2ZWxzKFBsb3RPYnNlcnZhdGlvbklEKSkgJT4lDQogIHB1bGwoUGxvdE9ic2VydmF0aW9uSUQpDQpHQU1fZGF0YV9pZHNfcTAgPC0gR0FNX2RhdGEgJT4lDQogICMgSm9pbiB0byBnZXQgYmlvZ2VvIGFuZCB1bml0DQogIGxlZnRfam9pbihkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgICAgICAgICAgIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgYmlvZ2VvLCB1bml0KSAlPiUNCiAgICAgICAgICAgICAgZGlzdGluY3QoKSkgJT4lDQogICMgSm9pbiB0byBnZXQgRVVOSVMgaW5mbw0KICAgbGVmdF9qb2luKGRiX0V1cm9wYV9hbGxvYnMgJT4lDQogICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjciwNCiAgICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IpKSAlPiUNCiAgIyBKb2luIHRvIGdldCBvcmlnaW5hbCB2YWx1ZXMgb2YgaW5kaWNlcw0KICBsZWZ0X2pvaW4oZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIERPWSwgTkRWSSwgRVZJLCBTQVZJKSAlPiUNCiAgICAgICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKE5EVkksIEVWSSwgU0FWSSksIG5hbWVzX3RvID0gImluZGV4IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWVfb3JpZyIpKSAlPiUNCiAgIyBKb2luIHRvIGdldCB0c19xdWFsaXR5IGRhdGENCiAgbGVmdF9qb2luKHRzX3F1YWxpdHlfZmxhZyAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBuX21vbnRocywgcXVhbGl0eV9mbGFnKSkgJT4lDQogICMgS2VlcCBvbmx5IHRob3NlIHdpdGggcXVhbGl0eV9mbGFnID09IDANCiAgZHBseXI6OmZpbHRlcihxdWFsaXR5X2ZsYWcgPT0gMCkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBHZXQgdW5pcXVlIFBsb3RPYnNlcnZhdGlvbklEcw0KdW5pcXVlX2lkczAgPC0gaWRzX3EwDQoNCiMgQ3JlYXRlIGFuZCBzdG9yZSBwbG90cyBpbiBhIGxpc3QNCnRzX3Bsb3RzX3EwIDwtIG1hcCh1bmlxdWVfaWRzMCwgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIEdBTV9kYXRhX2lkc19xMCAlPiUNCiAgICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBhcy5jaGFyYWN0ZXIoUGxvdE9ic2VydmF0aW9uSUQpKSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEID09IGlkKSANCiAgDQogICMgRXh0cmFjdCBtZXRhZGF0YSBmb3IgdGl0bGUNCiAgbWV0YWRhdGEgPC0gcGxvdF9kYXRhICU+JQ0KICAgIHNlbGVjdChiaW9nZW8sIHVuaXQsIEVVTklTYV8xLCBFVU5JU2FfMiwgcXVhbGl0eV9mbGFnKSAlPiUNCiAgICBkaXN0aW5jdCgpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwbG90X2RhdGEsYWVzKHggPSBET1ksIHkgPSB2YWx1ZV9vcmlnKSwgYWxwaGEgPSAwLjUpICsNCiAgICBnZW9tX2xpbmUoZGF0YSA9IHBsb3RfZGF0YSwgYWVzKHggPSBET1ksIHkgPSB2YWx1ZSksIA0KICAgICAgICAgICAgICBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHNvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgc29zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHBvcyksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHBvcywgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIGVvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgZW9zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoaW5kZXgpKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgie2lkfSB8IHttZXRhZGF0YSRiaW9nZW99e21ldGFkYXRhJHVuaXR9IHwge21ldGFkYXRhJEVVTklTYV8xfSB8IHttZXRhZGF0YSRFVU5JU2FfMn0gfCBRdWFsaXR5OiB7bWV0YWRhdGEkcXVhbGl0eV9mbGFnfSIpLA0KICAgICAgeCA9ICJEYXkgb2YgWWVhciIsDQogICAgICB5ID0gIkluZGV4IFZhbHVlIg0KICAgICkgKw0KICAgIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KfSkNCg0KIyBOYW1lIHRoZSBsaXN0IGJ5IFBsb3RPYnNlcnZhdGlvbklEDQpuYW1lcyh0c19wbG90c19xMCkgPC0gdW5pcXVlX2lkczANCg0KIyBEaXNwbGF5IHRoZSBmaXJzdCBwbG90DQp0c19wbG90c19xMFsxXQ0KYGBgDQoNClNhdmUgZWFjaCBwbG90IHRvIGEgZmlsZToNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCndhbGsyKHRzX3Bsb3RzX3EwLCBzZXFfYWxvbmcodHNfcGxvdHNfcTApLCB+IGdnc2F2ZSgNCiAgZmlsZW5hbWUgPSBwYXN0ZTAoIkM6L0FuYWx5c2VzL01PVElWQVRFX3ZhbGlkYXRpb24vb3V0cHV0L2ZpZ3VyZXMvcGhlbm9sb2d5L3RzX3EwX1MyL3RzX3Bsb3RzX3EwIiwgLnksICIuanBlZyIpLA0KICBwbG90ID0gLngsDQogIHdpZHRoID0gOCwNCiAgaGVpZ2h0ID0gNQ0KKSkNCmBgYA0KDQojIFNtb290aCB0aGUgdGltZSBzZXJpZXMgb2YgTkRNSSBhbmQgTkRXSQ0KDQpVc2luZyBHQU0sIHdpdGhvdXQgcmVwbGFjaW5nIHZhbHVlcyBpbiBET1kgMeKAkzUwIGFuZCBET1kgMzE14oCTZW5kIHdpdGggc2VwYXJhdGUgYmFzZSB2YWx1ZXMsIGxhdGVyIHVzZSBvbmx5IHVud2VpZ2h0ZWQgR0FNLg0KDQpgYGB7cn0NCmNvbXB1dGVfdW53ZWlnaHRlZF9maXQgPC0gZnVuY3Rpb24oDQogICAgIyBEYXRhIGZyYW1lIGRmIHdpdGggaW5kZXggdmFsdWVzIG92ZXIgdGltZSAoRE9ZKQ0KICAgIGRmLCANCiAgICAjIE5hbWUgb2YgdGhlIHZlZ2V0YXRpb24gaW5kaWNlcyBjb2x1bW5zIChlLmcuLCAiTkRWSSIsICJFVkkiLCAiU0FWSSkNCiAgICBpbmRleF9jb2xzID0gYygiTkRNSSIsICJORFdJIikNCikgew0KICAjIEluaXRpYWxpemUgbGlzdCB0byBzdG9yZSByZXN1bHRzDQogIGZpdHNfbGlzdCA8LSBsaXN0KCkNCiAgDQogICMgTG9vcCBvdmVyIGVhY2ggaW5kZXggY29sdW1uDQogIGZvciAoaW5kZXhfY29sIGluIGluZGV4X2NvbHMpIHsNCiAgICBkZl9pbmRleCA8LSBkZiAlPiUNCiAgICAgICMgUmVtb3ZlIHJvd3Mgd2l0aCBtaXNzaW5nIGluZGV4IHZhbHVlcyBhbmQgc29ydCBkYXRhIGJ5IERPWQ0KICAgICAgZmlsdGVyKCFpcy5uYSguZGF0YVtbaW5kZXhfY29sXV0pKSAlPiUgYXJyYW5nZShET1kpDQogICAgDQogICAgIyBFeHRyYWN0IHggKERPWSkgYW5kIHkgKGluZGV4KSB2ZWN0b3JzIGZvciBtb2RlbGxpbmcNCiAgICB4IDwtIGRmX2luZGV4JERPWQ0KICAgIHkgPC0gZGZfaW5kZXhbW2luZGV4X2NvbF1dDQogICAgDQogICAgIyBJZiB0aGVyZSBhcmUgZmV3ZXIgdGhhbiAxMSBvYnNlcnZhdGlvbnMgb3IgYWxsIHZhbHVlcyBhcmUgTkEsIHNraXANCiAgICBpZiAobGVuZ3RoKHgpIDwgMTEgfHwgYWxsKGlzLm5hKHkpKSkgew0KICAgICAgbmV4dA0KICAgIH0NCiAgICANCiAgICAjIEZpdCBHQU0gKHVud2VpZ2h0ZWQpIHdpdGggYSB0aGluIHBsYXRlIHNwbGluZSAoYnMgPSAidHAiKQ0KICAgICMgdG8gc21vb3RoIHRoZSBpbmRleCBjdXJ2ZQ0KICAgIGdhbV91bndlaWdodGVkIDwtIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpKQ0KICAgIHByZWQgPC0gcHJlZGljdChnYW1fdW53ZWlnaHRlZCwgbmV3ZGF0YSA9IHRpYmJsZSh4ID0geCkpDQogICAgDQogICAgIyBDcmVhdGUgdGliYmxlIHRvIHN0b3JlIG9yaWdpbmFsIGFuZCBwcmVkaWN0ZWQgaW5kZXggdmFsdWVzDQogICAgZml0c19kZiA8LSB0aWJibGUoDQogICAgICBQbG90T2JzZXJ2YXRpb25JRCA9IHVuaXF1ZShkZiRQbG90T2JzZXJ2YXRpb25JRCksDQogICAgICBET1kgPSB4LA0KICAgICAgaW5kZXggPSBpbmRleF9jb2wsDQogICAgICB2YWx1ZSA9IHByZWQNCiAgICApDQogICAgDQogICAgZml0c19saXN0W1tpbmRleF9jb2xdXSA8LSBmaXRzX2RmDQogIH0NCiAgDQogIGlmIChsZW5ndGgoZml0c19saXN0KSA9PSAwKSB7DQogICAgcmV0dXJuKHRpYmJsZSgpKQ0KICB9DQogIA0KICBiaW5kX3Jvd3MoZml0c19saXN0KQ0KfQ0KYGBgDQoNCkFwcGx5IHRoZSBmdW5jdGlvbjoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxKQ0KDQojIEFwcGx5IHRoZSBmdW5jdGlvbiB0byBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQpleGVjdXRpb25fdGltZSA8LSBzeXN0ZW0udGltZSh7DQogIHdpdGhfcHJvZ3Jlc3Moew0KICAgIHNtb290aGVkX2RhdGEgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgZ3JvdXBfc3BsaXQoUGxvdE9ic2VydmF0aW9uSUQpICU+JQ0KICAgICAgc2V0X25hbWVzKG1hcF9jaHIoLiwgfiBhcy5jaGFyYWN0ZXIodW5pcXVlKC54JFBsb3RPYnNlcnZhdGlvbklEKSkpKSAlPiUNCiAgICAgIGZ1dHVyZV9tYXBfZGZyKH4gY29tcHV0ZV91bndlaWdodGVkX2ZpdChkZiA9IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleF9jb2xzID0gYygiTkRNSSIsICJORFdJIikpLA0KICAgICAgICAgICAgICAgICAgICAgLnByb2dyZXNzID0gVFJVRSkNCiAgfSkNCn0pDQoNCnByaW50KGV4ZWN1dGlvbl90aW1lKQ0KYGBgDQoNCkxvb2s6DQoNCmBgYHtyfQ0Kc21vb3RoZWRfZGF0YQ0KYGBgDQoNClNhdmUgYXMgYW4gb2JqZWN0Og0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kc2F2ZShzbW9vdGhlZF9kYXRhLCBmaWxlID0gIm9iamVjdHMvc21vb3RoZWRfZGF0YV9TMi5SZGF0YSIpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIGVhY2ggUGxvdE9ic2VydmF0aW9uSUQNCg0KYGBge3J9DQpzbW9vdGhlZF9kYXRhX2lkcyA8LSBzbW9vdGhlZF9kYXRhICU+JQ0KICAjIEpvaW4gdG8gZ2V0IGJpb2dlbyBhbmQgdW5pdA0KICBsZWZ0X2pvaW4oZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGJpb2dlbywgdW5pdCkgJT4lDQogICAgICAgICAgICAgIGRpc3RpbmN0KCkpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IEVVTklTIGluZm8NCiAgIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzICU+JQ0KICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsDQogICAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyKSkgJT4lDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGFzLmNoYXJhY3RlcihQbG90T2JzZXJ2YXRpb25JRCkpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgR2V0IHVuaXF1ZSBQbG90T2JzZXJ2YXRpb25JRHMNCnVuaXF1ZV9pZHMgPC0gdW5pcXVlKHNtb290aGVkX2RhdGFfaWRzJFBsb3RPYnNlcnZhdGlvbklEKQ0KDQojIENyZWF0ZSBhbmQgc3RvcmUgcGxvdHMgaW4gYSBsaXN0DQp0c19wbG90c19ORE1JX05EV0k8LSBtYXAodW5pcXVlX2lkcywgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIHNtb290aGVkX2RhdGFfaWRzICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEID09IGlkKQ0KICANCiAgIyBFeHRyYWN0IG1ldGFkYXRhIGZvciB0aXRsZQ0KICBtZXRhZGF0YSA8LSBwbG90X2RhdGEgJT4lDQogICAgc2VsZWN0KGJpb2dlbywgdW5pdCwgRVVOSVNhXzEsIEVVTklTYV8yKSAlPiUNCiAgICBkaXN0aW5jdCgpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICAgZ2VvbV9wb2ludChkYXRhID0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBET1ksIE5ETUksIE5EV0kpICU+JQ0KICAgICAgICAgICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKE5ETUksIE5EV0kpLCBuYW1lc190byA9ICJpbmRleCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lDQogICAgICAgICAgICAgICAgICBmaWx0ZXIoUGxvdE9ic2VydmF0aW9uSUQgPT0gaWQpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gRE9ZLCB5ID0gdmFsdWUpLCBhbHBoYSA9IDAuNikgKw0KICAgIGdlb21fbGluZShkYXRhID0gcGxvdF9kYXRhLCBhZXMoeCA9IERPWSwgeSA9IHZhbHVlKSwNCiAgICAgICAgICAgICAgc2l6ZSA9IDAuNSwgY29sb3IgPSAiYmx1ZSIpICsNCiAgICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKGluZGV4KSkgKw0KICAgIGxhYnMoDQogICAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIntpZH0gfCB7bWV0YWRhdGEkYmlvZ2VvfXttZXRhZGF0YSR1bml0fSB8IHttZXRhZGF0YSRFVU5JU2FfMX0gfCB7bWV0YWRhdGEkRVVOSVNhXzJ9IiksDQogICAgICB4ID0gIkRheSBvZiBZZWFyIiwNCiAgICAgIHkgPSAiSW5kZXggVmFsdWUiDQogICAgKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQp9KQ0KDQojIE5hbWUgdGhlIGxpc3QgYnkgUGxvdE9ic2VydmF0aW9uSUQNCm5hbWVzKHRzX3Bsb3RzX05ETUlfTkRXSSkgPC0gdW5pcXVlX2lkcw0KDQojIERpc3BsYXkgdGhlIGZpcnN0IHBsb3QNCnByaW50KHRzX3Bsb3RzX05ETUlfTkRXSVtbMV1dKQ0KYGBgDQoNClNhdmUgZWFjaCBwbG90IHRvIGEgZmlsZToNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCndhbGsyKHRzX3Bsb3RzX05ETUlfTkRXSSwgc2VxX2Fsb25nKHRzX3Bsb3RzX05ETUlfTkRXSSksIH4gZ2dzYXZlKA0KICBmaWxlbmFtZSA9IHBhc3RlMCgiQzovQW5hbHlzZXMvTU9USVZBVEVfdmFsaWRhdGlvbi9vdXRwdXQvZmlndXJlcy9waGVub2xvZ3kvdHNfTkRNSV9ORFdJX1MyL3RzX3Bsb3RzX05EVklfTkRNSSIsDQogICAgICAgICAgICAgICAgICAgIC55LCAiLmpwZWciKSwNCiAgcGxvdCA9IC54LA0KICB3aWR0aCA9IDgsDQogIGhlaWdodCA9IDUNCikpDQpgYGANCg0KIyBHZXQgaW5kaWNlcyBkYXRhIChtYXguIGFuZCBtaW4uKQ0KDQpDYXJlZnVsISBUaGVzZSBtYXhpbXVtIGFuZCBtaW5pbXVtIHZhbHVlcyBhcmUgZnJvbSB0aGUgc21vb3RoZWQgdGltZSBzZXJpZXMuIEZvciBORFZJIC8gRVZJIC8gU0FWSSB2YWx1ZXMgaW4gRE9ZIDHigJM1MCBhbmQgRE9ZIDMxNeKAk2VuZCwgcmVtZW1iZXIgdGhhdCB0aGUgR0FNIHNtb290aGluZyBmdW5jdGlvbiByZXBsYWNlZCB0aGUgb3JpZ2luYWwgdmFsdWVzIHdpdGggdGhlIG1lYW4gYmFzZSB2YWx1ZSBvZiBvYnNlcnZhdGlvbnMgZHVyaW5nIGVhY2ggb2YgdGhlc2UgcmVzcGVjdGl2ZSBwZXJpb2RzLiBUaGlzIHdhcyBzbyBmYXIgbm90IGRvbmUgZm9yIE5ETUkgYW5kIE5EV0kuIA0KDQpgYGB7cn0NCmZpbmFsX2luZGljZXNfZGF0YSA8LSBHQU1fZGF0YSAlPiUNCiAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4KSAlPiUNCiAgc3VtbWFyaXNlKG1heCA9IG1heCh2YWx1ZSksIG1pbiA9IG1pbih2YWx1ZSkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBpbmRleCwgdmFsdWVzX2Zyb20gPSBjKG1heCwgbWluKSwNCiAgICAgICAgICAgICAgbmFtZXNfZ2x1ZSA9ICJ7aW5kZXh9X3sudmFsdWV9IikgJT4lDQogIGZ1bGxfam9pbigNCiAgICBzbW9vdGhlZF9kYXRhICU+JQ0KICAgICAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4KSAlPiUNCiAgICAgIHN1bW1hcmlzZShtYXggPSBtYXgodmFsdWUpLCBtaW4gPSBtaW4odmFsdWUpKSAlPiUNCiAgICAgIHVuZ3JvdXAoKSAlPiUNCiAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBpbmRleCwgdmFsdWVzX2Zyb20gPSBjKG1heCwgbWluKSwNCiAgICAgICAgICAgICAgICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97LnZhbHVlfSIpDQogICAgKQ0KYGBgDQoNCiMgR2V0IHBoZW5vbG9neSBkYXRhDQoNClVzZSBHQU0gaXRlcl8zIHRvIGdldCBkYXRlcyBvZiB0aGUgbW9tZW50cywgdmFsdWVzIGF0IHRob3NlIG1vbWVudHMgYW5kIEFVQyAodGltZS1pbnRlZ3JhdGVkIGluZGljZXMpIGJldHdlZW4gU09TIGFuZCBFT1M6DQoNCmBgYHtyfQ0KIyBKb2luIHRvIGdldCB2YWx1ZXMgYXQgU09TLCBQT1MsIEVPUyBhbmQgYXVjDQpmaW5hbF9waGVub2xvZ3lfZGF0YSA8LSBHQU1fZGF0YSAlPiUNCiAgbXV0YXRlKA0KICAgIHN0YWdlID0gY2FzZV93aGVuKA0KICAgICAgRE9ZID09IHNvc19zbG9wZSB+ICJzb3Nfc2xvcGUiLA0KICAgICAgRE9ZID09IHNvc190aHJlc2hvbGQgfiAic29zX3RocmVzaG9sZCIsDQogICAgICBET1kgPT0gcG9zIH4gInBvcyIsDQogICAgICBET1kgPT0gZW9zX3Nsb3BlIH4gImVvc19zbG9wZSIsDQogICAgICBET1kgPT0gZW9zX3RocmVzaG9sZCB+ICJlb3NfdGhyZXNob2xkIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApICU+JQ0KICBkcGx5cjo6ZmlsdGVyKCFpcy5uYShzdGFnZSkpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4LCBzdGFnZSwgZG95ID0gRE9ZLCB2YWx1ZSkgJT4lDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBjKGluZGV4LCBzdGFnZSksDQogICAgdmFsdWVzX2Zyb20gPSBjKGRveSwgdmFsdWUpLA0KICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97c3RhZ2V9X3sudmFsdWV9Ig0KICApICU+JQ0KICAjIENvbnZlcnQgbGlzdCBjb2xzIHRvIHJlZ3VsYXIgbnVtZXJpYyBjb2xzDQogIG11dGF0ZSgNCiAgICBORFZJX3Nvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoTkRWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIE5EVklfc29zX3RocmVzaG9sZF92YWx1ZSA9IG1hcF9kYmwoTkRWSV9zb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBORFZJX3Bvc192YWx1ZSA9IG1hcF9kYmwoTkRWSV9wb3NfdmFsdWUsIDEpLA0KICAgIE5EVklfZW9zX3Nsb3BlX3ZhbHVlID0gbWFwX2RibChORFZJX2Vvc19zbG9wZV92YWx1ZSwgMSksDQogICAgTkRWSV9lb3NfdGhyZXNob2xkX3ZhbHVlID0gbWFwX2RibChORFZJX2Vvc190aHJlc2hvbGRfdmFsdWUsIDEpLA0KICAgIEVWSV9zb3Nfc2xvcGVfdmFsdWUgPSBtYXBfZGJsKEVWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIEVWSV9zb3NfdGhyZXNob2xkX3ZhbHVlID0gbWFwX2RibChFVklfc29zX3RocmVzaG9sZF92YWx1ZSwgMSksDQogICAgRVZJX3Bvc192YWx1ZSA9IG1hcF9kYmwoRVZJX3Bvc192YWx1ZSwgMSksDQogICAgRVZJX2Vvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoRVZJX2Vvc19zbG9wZV92YWx1ZSwgMSksDQogICAgRVZJX2Vvc190aHJlc2hvbGRfdmFsdWUgPSBtYXBfZGJsKEVWSV9lb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBTQVZJX3Nvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoU0FWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIFNBVklfc29zX3RocmVzaG9sZF92YWx1ZSA9IG1hcF9kYmwoU0FWSV9zb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBTQVZJX3Bvc192YWx1ZSA9IG1hcF9kYmwoU0FWSV9wb3NfdmFsdWUsIDEpLA0KICAgIFNBVklfZW9zX3Nsb3BlX3ZhbHVlID0gbWFwX2RibChTQVZJX2Vvc19zbG9wZV92YWx1ZSwgMSksDQogICAgU0FWSV9lb3NfdGhyZXNob2xkX3ZhbHVlID0gbWFwX2RibChTQVZJX2Vvc190aHJlc2hvbGRfdmFsdWUsIDEpDQogICkgJT4lDQogIGZ1bGxfam9pbihHQU1fZGF0YSAlPiUNCiAgICAgICAgICAgICAgZGlzdGluY3QoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4LCBhdWNfc2xvcGUsIGF1Y190aHJlc2hvbGQpICU+JQ0KICAgICAgICAgICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gaW5kZXgsIHZhbHVlc19mcm9tID0gYyhhdWNfc2xvcGUsIGF1Y190aHJlc2hvbGQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lc19nbHVlID0gIntpbmRleH1fey52YWx1ZX0iKSkNCmBgYA0KDQojIEpvaW4gaW5kaWNlcyBhbmQgcGhlbm9sb2d5IGRhdGENCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZ1bGxfam9pbigNCiAgIyBJbmRpY2VzIGRhdGEgKG1heCBhbmQgbWluKQ0KICBmaW5hbF9pbmRpY2VzX2RhdGEsDQogICMgQXZlcmFnZSB2YWx1ZXMgb2YgaW5kaWNlcyBwZXIgbW9udGgNCiAgbW9udGhseV9hdmdfaW5kaWNlcyAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gaW5kZXgsIHZhbHVlc19mcm9tID0gYyhhdmdfdmFsdWVfMDE6YXVjX21hcl9vY3QpLA0KICAgICAgICAgICAgICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97LnZhbHVlfSIpDQogICkgJT4lDQogIGZ1bGxfam9pbigNCiAgICAjIFBoZW5vbG9neSBkYXRhDQogICAgZmluYWxfcGhlbm9sb2d5X2RhdGEgDQogICAgKSAlPiUNCiAgIyBTb3J0IGNvbHMgaW4gYWxwaGFiZXRpY2FsIG9yZGVyDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgc29ydChuYW1lcyguKVtuYW1lcyguKSAhPSAiUGxvdE9ic2VydmF0aW9uSUQiXSkpDQpgYGANCg0KIyBBZGQgRVVOSVMgY29kZXMNCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzIDwtIGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgbGVmdF9qb2luKGRiX0V1cm9wYV9hbGxvYnMpDQpgYGANCg0KIyBNb250aGx5IHNwZWN0cm9waGVub2xvZ3kgcGVyIGhhYml0YXQgdHlwZQ0KDQpgYGB7cn0NCiMgUHJlcGFyZSB0aGUgZGF0YQ0KZGF0YV9tb250aGx5X0VVTklTYV8xIDwtIGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgbXV0YXRlKG1vbnRoID0gbW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFLCBhYmJyID0gVFJVRSwgbG9jYWxlPSJFTi11cyIpKSAlPiUNCiAgZ3JvdXBfYnkobW9udGgsIEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjcikgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBtZWFuX05EVkkgPSBtZWFuKE5EVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfTkRWSSA9IHNkKE5EVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9ORFZJID0gc3VtKCFpcy5uYShORFZJKSksDQogICAgbWVhbl9FVkkgPSBtZWFuKEVWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBzZF9FVkkgPSBzZChFVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9FVkkgPSBzdW0oIWlzLm5hKEVWSSkpLA0KICAgIG1lYW5fU0FWSSA9IG1lYW4oU0FWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBzZF9TQVZJID0gc2QoU0FWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBuX1NBVkkgPSBzdW0oIWlzLm5hKFNBVkkpKSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkNCg0KZGF0YV9tb250aGx5X0VVTklTYV8xIDwtIGRhdGFfbW9udGhseV9FVU5JU2FfMSAlPiUNCiAgIyBBZGQgbGFiZWwgd2l0aCBuDQogIGxlZnRfam9pbihkYXRhX21vbnRobHlfRVVOSVNhXzEgJT4lDQogICAgICAgICAgICAgIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgICAgICAgICAgICAgc3VtbWFyaXNlKG5fdG90YWwgPSBzdW0obl9ORFZJLCBuYS5ybSA9IFRSVUUpKSwgDQogICAgICAgICAgICBieSA9ICJFVU5JU2FfMSIpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzFfbGFiZWwgPSBwYXN0ZTAoRVVOSVNhXzEsICIgKG4gPSAiLCBuX3RvdGFsLCAiKSIpKQ0KDQpkYXRhX21vbnRobHlfRVVOSVNhXzIgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICBtdXRhdGUobW9udGggPSBtb250aChkYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFLCBsb2NhbGU9IkVOLXVzIikpICU+JQ0KICBncm91cF9ieShtb250aCwgRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLCBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbWVhbl9ORFZJID0gbWVhbihORFZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIHNkX05EVkkgPSBzZChORFZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIG5fTkRWSSA9IHN1bSghaXMubmEoTkRWSSkpLA0KICAgIG1lYW5fRVZJID0gbWVhbihFVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfRVZJID0gc2QoRVZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIG5fRVZJID0gc3VtKCFpcy5uYShFVkkpKSwNCiAgICBtZWFuX1NBVkkgPSBtZWFuKFNBVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfU0FWSSA9IHNkKFNBVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9TQVZJID0gc3VtKCFpcy5uYShTQVZJKSksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApDQoNCmRhdGFfbW9udGhseV9FVU5JU2FfMiA8LSBkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lDQogICMgQWRkIGxhYmVsIHdpdGggbg0KICBsZWZ0X2pvaW4oZGF0YV9tb250aGx5X0VVTklTYV8yICU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShFVU5JU2FfMikgJT4lDQogICAgICAgICAgICAgIHN1bW1hcmlzZShuX3RvdGFsID0gc3VtKG5fTkRWSSwgbmEucm0gPSBUUlVFKSksIA0KICAgICAgICAgICAgYnkgPSAiRVVOSVNhXzIiKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8yX2xhYmVsID0gcGFzdGUwKEVVTklTYV8yLCAiIChuID0gIiwgbl90b3RhbCwgIikiKSkNCmBgYA0KDQojIyBFVU5JUyBsZXZlbCAxDQoNCmBgYHtyfQ0KIyBQbG90cw0KDQojIEVVTklTYV8xDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8xICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzFfbGFiZWwsIEVVTklTYV8xX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fTkRWSSwgY29sb3IgPSBFVU5JUywgDQogICAgICAgICAgIGdyb3VwID0gRVVOSVNhXzFfbGFiZWwpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBFVU5JU2FfMSkpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IE5EVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIk5EVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMV9ORFZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDYsIGhlaWdodCA9IDIuNSkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8xX2xhYmVsLCBFVU5JU2FfMV9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgDQogICAgICAgICAgIGdyb3VwID0gRVVOSVNhXzFfbGFiZWwpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX0VWSSAtIHNkX0VWSSwgeW1heCA9IG1lYW5fRVZJICsgc2RfRVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBFVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIkVWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMxKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMxX0VWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA2LCBoZWlnaHQgPSAyLjUpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzEgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMV9sYWJlbCwgRVVOSVNhXzFfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9TQVZJLCBjb2xvciA9IEVVTklTLCANCiAgICAgICAgICAgZ3JvdXAgPSBFVU5JU2FfMV9sYWJlbCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzEpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzFfU0FWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA2LCBoZWlnaHQgPSAyLjUpDQpgYGANCg0KIyMgRVVOSVMgbGV2ZWwgMg0KDQojIyMgUQ0KDQpgYGB7cn0NCiMgTkRWSQ0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJRIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBSZW1vdmUgdGhvc2Ugd2l0aCBFVU5JUyBsZXZlbCAyIHRoYXQgZG9lcyBub3QgbWF0Y2ggY3VycmVudCBjbGFzc2lmDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8yICE9ICJRYSIgJiBFVU5JU2FfMiAhPSAiUWIiKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1FfTkRWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSAyLjUpDQoNCiMgRVZJDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIFJlbW92ZSB0aG9zZSB3aXRoIEVVTklTIGxldmVsIDIgdGhhdCBkb2VzIG5vdCBtYXRjaCBjdXJyZW50IGNsYXNzaWYNCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzIgIT0gIlFhIiAmIEVVTklTYV8yICE9ICJRYiIpICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzJfbGFiZWwsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fRVZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9FVkkgLSBzZF9FVkksIHltYXggPSBtZWFuX0VWSSArIHNkX0VWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgRVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJFVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMl9RX0VWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSAyLjUpDQoNCiMgU0FWSQ0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUNCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIFJlbW92ZSB0aG9zZSB3aXRoIEVVTklTIGxldmVsIDIgdGhhdCBkb2VzIG5vdCBtYXRjaCBjdXJyZW50IGNsYXNzaWYNCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzIgIT0gIlFhIiAmIEVVTklTYV8yICE9ICJRYiIpICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzJfbGFiZWwsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fU0FWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzJfUV9TQVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDcsIGhlaWdodCA9IDIuNSkNCmBgYA0KDQojIyMgUg0KDQpgYGB7cn0NCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIA0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSA9PSAiUiIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1JfTkRWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSAyLjUpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIA0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSA9PSAiUiIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fRVZJIC0gc2RfRVZJLCB5bWF4ID0gbWVhbl9FVkkgKyBzZF9FVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IEVWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiRVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzJfUl9FVkkuanBlZyIpLA0KICBkcGkgPSAzMDAsIHdpZHRoID0gNywgaGVpZ2h0ID0gMi41KQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlIiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9TQVZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9TQVZJIC0gc2RfU0FWSSwgeW1heCA9IG1lYW5fU0FWSSArIHNkX1NBVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IFNBVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIlNBVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMl9SX1NBVkkuanBlZyIpLA0KICBkcGkgPSAzMDAsIHdpZHRoID0gNywgaGVpZ2h0ID0gMi41KQ0KYGBgDQoNCiMjIyBTDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJTIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBSZW1vdmUgdGhvc2Ugd2l0aCBFVU5JUyBsZXZlbCAyIHRoYXQgZG9lcyBub3QgbWF0Y2ggY3VycmVudCBjbGFzc2lmDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8yICE9ICJTYSIgJiBFVU5JU2FfMiAhPSAiU2IiKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1NfTkRWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA4LCBoZWlnaHQgPSAyLjUpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIA0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSA9PSAiUyIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgICMgUmVtb3ZlIHRob3NlIHdpdGggRVVOSVMgbGV2ZWwgMiB0aGF0IGRvZXMgbm90IG1hdGNoIGN1cnJlbnQgY2xhc3NpZg0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMiAhPSAiU2EiICYgRVVOSVNhXzIgIT0gIlNiIikgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9FVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX0VWSSAtIHNkX0VWSSwgeW1heCA9IG1lYW5fRVZJICsgc2RfRVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBFVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIkVWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1NfRVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDgsIGhlaWdodCA9IDIuNSkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJTIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBSZW1vdmUgdGhvc2Ugd2l0aCBFVU5JUyBsZXZlbCAyIHRoYXQgZG9lcyBub3QgbWF0Y2ggY3VycmVudCBjbGFzc2lmDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8yICE9ICJTYSIgJiBFVU5JU2FfMiAhPSAiU2IiKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX1NBVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX1NBVkkgLSBzZF9TQVZJLCB5bWF4ID0gbWVhbl9TQVZJICsgc2RfU0FWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgU0FWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiU0FWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1NfU0FWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA4LCBoZWlnaHQgPSAyLjUpDQpgYGANCg0KIyMjIFQNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9ORFZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9ORFZJIC0gc2RfTkRWSSwgeW1heCA9IG1lYW5fTkRWSSArIHNkX05EVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IE5EVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIk5EVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMl9UX05EVkkuanBlZyIpLA0KICBkcGkgPSAzMDAsIHdpZHRoID0gNiwgaGVpZ2h0ID0gMi41KQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9FVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX0VWSSAtIHNkX0VWSSwgeW1heCA9IG1lYW5fRVZJICsgc2RfRVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBFVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIkVWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1RfRVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDYsIGhlaWdodCA9IDIuNSkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJUIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzJfbGFiZWwsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fU0FWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzJfVF9TQVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDYsIGhlaWdodCA9IDIuNSkNCmBgYA0KDQojIENhbGN1bGF0ZSBvdGhlciBwaGVub2xvZ2ljYWwgbWV0cmljcw0KDQpgYGB7cn0NCmZpbmFsX1JTX2RhdGEgPC0gZmluYWxfUlNfZGF0YSAlPiUNCiAgbXV0YXRlKA0KICAgICMgd2l0aCBzbG9wZSBtZXRob2QNCiAgICAjIEdyb3dpbmcgc2Vhc29uIGR1cmF0aW9uDQogICAgTkRWSV9zbG9wZV9nc2QgPSBORFZJX2Vvc19zbG9wZV9kb3kgLSBORFZJX3Nvc19zbG9wZV9kb3ksDQogICAgRVZJX3Nsb3BlX2dzZCA9IE5EVklfZW9zX3Nsb3BlX2RveSAtIE5EVklfc29zX3Nsb3BlX2RveSwNCiAgICBTQVZJX3Nsb3BlX2dzZCA9IFNBVklfZW9zX3Nsb3BlX2RveSAtIFNBVklfc29zX3Nsb3BlX2RveSwNCiAgICAjIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIHNvcw0KICAgIE5EVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlID0gTkRWSV9wb3NfdmFsdWUgLSBORFZJX3Nvc19zbG9wZV92YWx1ZSwNCiAgICBFVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlID0gRVZJX3Bvc192YWx1ZSAtIEVWSV9zb3Nfc2xvcGVfdmFsdWUsDQogICAgU0FWSV9kaWZmX3Bvc19zb3Nfc2xvcGVfdmFsdWUgPSBTQVZJX3Bvc192YWx1ZSAtIFNBVklfc29zX3Nsb3BlX3ZhbHVlLA0KICAgICMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgZW9zDQogICAgTkRWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfdmFsdWUgPSBORFZJX3Bvc192YWx1ZSAtIE5EVklfZW9zX3Nsb3BlX3ZhbHVlLA0KICAgIEVWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfdmFsdWUgPSBFVklfcG9zX3ZhbHVlIC0gRVZJX2Vvc19zbG9wZV92YWx1ZSwNCiAgICBTQVZJX2RpZmZfcG9zX2Vvc19zbG9wZV92YWx1ZSA9IFNBVklfcG9zX3ZhbHVlIC0gU0FWSV9lb3Nfc2xvcGVfdmFsdWUsDQogICAgIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgc29zDQogICAgTkRWSV9kaWZmX3Bvc19zb3Nfc2xvcGVfZG95ID0gTkRWSV9wb3NfZG95IC0gTkRWSV9zb3Nfc2xvcGVfZG95LA0KICAgIEVWSV9kaWZmX3Bvc19zb3Nfc2xvcGVfZG95ID0gRVZJX3Bvc19kb3kgLSBFVklfc29zX3Nsb3BlX2RveSwNCiAgICBTQVZJX2RpZmZfcG9zX3Nvc19zbG9wZV9kb3kgPSBTQVZJX3Bvc19kb3kgLSBTQVZJX3Nvc19zbG9wZV9kb3ksDQogICAgIyBBYnNvbHV0ZSBkaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgZW9zDQogICAgTkRWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfZG95ID0gYWJzKE5EVklfcG9zX2RveSAtIE5EVklfZW9zX3Nsb3BlX2RveSksDQogICAgRVZJX2RpZmZfcG9zX2Vvc19zbG9wZV9kb3kgPSBhYnMoRVZJX3Bvc19kb3kgLSBFVklfZW9zX3Nsb3BlX2RveSksDQogICAgU0FWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfZG95ID0gYWJzKFNBVklfcG9zX2RveSAtIFNBVklfZW9zX3Nsb3BlX2RveSksDQogICAgIyBXaXRoIHRocmVzaG9sZCBtZXRob2QNCiAgICAjIEdyb3dpbmcgc2Vhc29uIGR1cmF0aW9uDQogICAgTkRWSV90aHJlc2hvbGRfZ3NkID0gTkRWSV9lb3NfdGhyZXNob2xkX2RveSAtIE5EVklfc29zX3RocmVzaG9sZF9kb3ksDQogICAgRVZJX3RocmVzaG9sZF9nc2QgPSBORFZJX2Vvc190aHJlc2hvbGRfZG95IC0gTkRWSV9zb3NfdGhyZXNob2xkX2RveSwNCiAgICBTQVZJX3RocmVzaG9sZF9nc2QgPSBTQVZJX2Vvc190aHJlc2hvbGRfZG95IC0gU0FWSV9zb3NfdGhyZXNob2xkX2RveSwNCiAgICAjIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIHNvcw0KICAgIE5EVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF92YWx1ZSA9DQogICAgICBORFZJX3Bvc192YWx1ZSAtIE5EVklfc29zX3RocmVzaG9sZF92YWx1ZSwNCiAgICBFVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF92YWx1ZSA9IA0KICAgICAgRVZJX3Bvc192YWx1ZSAtIEVWSV9zb3NfdGhyZXNob2xkX3ZhbHVlLA0KICAgIFNBVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF92YWx1ZSA9IA0KICAgICAgU0FWSV9wb3NfdmFsdWUgLSBTQVZJX3Nvc190aHJlc2hvbGRfdmFsdWUsDQogICAgIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBlb3MNCiAgICBORFZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfdmFsdWUgPQ0KICAgICAgTkRWSV9wb3NfdmFsdWUgLSBORFZJX2Vvc190aHJlc2hvbGRfdmFsdWUsDQogICAgRVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfdmFsdWUgPSANCiAgICAgIEVWSV9wb3NfdmFsdWUgLSBFVklfZW9zX3RocmVzaG9sZF92YWx1ZSwNCiAgICBTQVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfdmFsdWUgPSANCiAgICAgIFNBVklfcG9zX3ZhbHVlIC0gU0FWSV9lb3NfdGhyZXNob2xkX3ZhbHVlLA0KICAgICMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIHNvcw0KICAgIE5EVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF9kb3kgPSBORFZJX3Bvc19kb3kgLSBORFZJX3Nvc190aHJlc2hvbGRfZG95LA0KICAgIEVWSV9kaWZmX3Bvc19zb3NfdGhyZXNob2xkX2RveSA9IEVWSV9wb3NfZG95IC0gRVZJX3Nvc190aHJlc2hvbGRfZG95LA0KICAgIFNBVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF9kb3kgPSBTQVZJX3Bvc19kb3kgLSBTQVZJX3Nvc190aHJlc2hvbGRfZG95LA0KICAgICMgQWJzb2x1dGUgZGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIGVvcw0KICAgIE5EVklfZGlmZl9wb3NfZW9zX3RocmVzaG9sZF9kb3kgPSANCiAgICAgIGFicyhORFZJX3Bvc19kb3kgLSBORFZJX2Vvc190aHJlc2hvbGRfZG95KSwNCiAgICBFVklfZGlmZl9wb3NfZW9zX3RocmVzaG9sZF9kb3kgPSANCiAgICAgIGFicyhFVklfcG9zX2RveSAtIEVWSV9lb3NfdGhyZXNob2xkX2RveSksDQogICAgU0FWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX2RveSA9IA0KICAgICAgYWJzKFNBVklfcG9zX2RveSAtIFNBVklfZW9zX3RocmVzaG9sZF9kb3kpLA0KICAgICMgV2l0aCBtb250aHMgbWV0aG9kDQogICAgIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBtYXJjaA0KICAgIE5EVklfZGlmZl9wb3NfbWFyY2hfdmFsdWUgPSBORFZJX3Bvc192YWx1ZSAtIE5EVklfYXZnX3ZhbHVlXzAzLA0KICAgIEVWSV9kaWZmX3Bvc19tYXJjaF92YWx1ZSA9IEVWSV9wb3NfdmFsdWUgLSBFVklfYXZnX3ZhbHVlXzAzLA0KICAgIFNBVklfZGlmZl9wb3NfbWFyY2hfdmFsdWUgPSBTQVZJX3Bvc192YWx1ZSAtIFNBVklfYXZnX3ZhbHVlXzAzLA0KICAgICMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgb2N0DQogICAgTkRWSV9kaWZmX3Bvc19vY3RfdmFsdWUgPSBORFZJX3Bvc192YWx1ZSAtIE5EVklfYXZnX3ZhbHVlXzEwLA0KICAgIEVWSV9kaWZmX3Bvc19vY3RfdmFsdWUgPSBFVklfcG9zX3ZhbHVlIC0gRVZJX2F2Z192YWx1ZV8xMCwNCiAgICBTQVZJX2RpZmZfcG9zX29jdF92YWx1ZSA9IFNBVklfcG9zX3ZhbHVlIC0gU0FWSV9hdmdfdmFsdWVfMTAsDQogICAgIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgbWFyY2gNCiAgICBORFZJX2RpZmZfcG9zX21hcmNoX2RveSA9IE5EVklfcG9zX2RveSAtIDc1LCANCiAgICBFVklfZGlmZl9wb3NfbWFyY2hfZG95ID0gRVZJX3Bvc19kb3kgLSA3NSwNCiAgICBTQVZJX2RpZmZfcG9zX21hcmNoX2RveSA9IFNBVklfcG9zX2RveSAtIDc1LA0KICAgICMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIG9jdA0KICAgIE5EVklfZGlmZl9wb3Nfb2N0X2RveSA9IGFicyhORFZJX3Bvc19kb3kgLSAyODUpLA0KICAgIEVWSV9kaWZmX3Bvc19vY3RfZG95ID0gYWJzKEVWSV9wb3NfZG95IC0gMjg1KSwNCiAgICBTQVZJX2RpZmZfcG9zX29jdF9kb3kgPSBhYnMoU0FWSV9wb3NfZG95IC0gMjg1KQ0KICApDQpgYGANCg0KIyMgQ2hlY2tzDQoNCiMjIyBTbG9wZSBtZXRob2QNCg0KYGBge3J9DQojIEdyb3dpbmcgc2Vhc29uIGR1cmF0aW9uIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JSANCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfc2xvcGVfZ3NkIDw9IDAgfCBFVklfc2xvcGVfZ3NkIDw9IDAgfCANCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9zbG9wZV9nc2QgPD0gMCkpDQojIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIHNvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX3Nvc19zbG9wZV92YWx1ZSA8PSAwKSkNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKFNBVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBlb3Mgc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihORFZJX2RpZmZfcG9zX2Vvc19zbG9wZV92YWx1ZSA8PSAwKSkNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfdmFsdWUgPD0gMCkpDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihTQVZJX2RpZmZfcG9zX2Vvc19zbG9wZV92YWx1ZSA8PSAwKSkNCiMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIHNvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3Nfc29zX3Nsb3BlX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX3Nvc19zbG9wZV9kb3kgPD0gMCB8DQogICAgICAgICAgICAgICAgICAgICAgIFNBVklfZGlmZl9wb3Nfc29zX3Nsb3BlX2RveSA8PSAwKSkNCiMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBlb3MgYW5kIHBvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3NfZW9zX3Nsb3BlX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX2Vvc19zbG9wZV9kb3kgPD0gMCB8DQogICAgICAgICAgICAgICAgICAgICAgIFNBVklfZGlmZl9wb3NfZW9zX3Nsb3BlX2RveSA8PSAwKSkNCmBgYA0KDQojIyMgVGhyZXNob2xkIG1ldGhvZA0KDQpgYGB7cn0NCiMgR3Jvd2luZyBzZWFzb24gZHVyYXRpb24gc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lIA0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV90aHJlc2hvbGRfZ3NkIDw9IDAgfCBFVklfdGhyZXNob2xkX2dzZCA8PSAwIHwgDQogICAgICAgICAgICAgICAgICAgICAgIFNBVklfdGhyZXNob2xkX2dzZCA8PSAwKSkNCiMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgc29zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19zb3NfdGhyZXNob2xkX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfdmFsdWUgPD0gMCkpDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihTQVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfdmFsdWUgPD0gMCkpDQojIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIGVvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3NfZW9zX3RocmVzaG9sZF92YWx1ZSA8PSAwKSkNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoU0FWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX3ZhbHVlIDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgc29zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19zb3NfdGhyZXNob2xkX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfZG95IDw9IDAgfA0KICAgICAgICAgICAgICAgICAgICAgICBTQVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfZG95IDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIGVvcyBhbmQgcG9zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfZG95IDw9IDAgfA0KICAgICAgICAgICAgICAgICAgICAgICBTQVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfZG95IDw9IDApKQ0KYGBgDQoNCiMgSEVSRTogUnVuLiBEZXRlY3QgbnVtYmVyIG9mIHBlYWtzIGluIHNtb290aGVkIGN1cnZlcw0KDQpgYGB7cn0NCiMgU2V0IHVwIHBhcmFsbGVsIHBsYW4NCnBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gbWluKHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gMSkpDQoNCiMgRGVmaW5lIHBlYWstY291bnRpbmcgZnVuY3Rpb24NCmNvdW50X3BlYWtzIDwtIGZ1bmN0aW9uKGRmKSB7DQogICMgQ29udmVydCAxRCBhcnJheSBjb2x1bW4gdG8gbnVtZXJpYyB2ZWN0b3INCiAgeSA8LSBhcy5udW1lcmljKGRmJHZhbHVlKQ0KICANCiAgcGVha3MgPC0gZmluZHBlYWtzKHksIG1pbnBlYWtkaXN0YW5jZSA9IDMwLCB0aHJlc2hvbGQgPSAwLjAyKQ0KICANCiAgdGliYmxlKA0KICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmJFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICBpbmRleCA9IHVuaXF1ZShkZiRpbmRleCksDQogICAgbnVtX3BlYWtzID0gaWYgKCFpcy5udWxsKHBlYWtzKSkgbnJvdyhwZWFrcykgZWxzZSAwDQogICkNCn0NCg0KIyBBcHBseSBwZWFrIGNvdW50aW5nIGluIHBhcmFsbGVsDQpwZWFrX2NvdW50cyA8LSBHQU1fZGF0YSAlPiUNCiAgZmlsdGVyKGZpdF90eXBlID09ICJpdGVyXzMiKSAlPiUNCiAgYXJyYW5nZShET1kpICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgpICU+JQ0KICBuZXN0KCkgJT4lDQogIG11dGF0ZShyZXN1bHQgPSBmdXR1cmVfbWFwKGRhdGEsIGNvdW50X3BlYWtzLCAucHJvZ3Jlc3MgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAub3B0aW9ucyA9IGZ1cnJyX29wdGlvbnMoc2NoZWR1bGluZyA9IEluZikpKSAlPiUNCiAgc2VsZWN0KC1kYXRhKSAlPiUNCiAgdW5uZXN0KHJlc3VsdCkNCg0KIyBTdW1tYXJpemUgcmVzdWx0DQpwZWFrX2NvdW50c19zdW1tYXJ5IDwtIHBlYWtfY291bnRzICU+JSBjb3VudChpbmRleCwgbnVtX3BlYWtzKQ0KYGBgDQoNCiMgSEVSRTogc2F2ZQ0KDQpgYGB7cn0NCiMgIyBGdW5jdGlvbiB0byBjb3VudCBwZWFrcyBmb3IgZWFjaCBQbG90T2JzZXJ2YXRpb25JRA0KIyBjb3VudF9wZWFrcyA8LSBmdW5jdGlvbihkZikgew0KIyAgIHkgPC0gZGYkdmFsdWUNCiMgICBwZWFrcyA8LSBmaW5kcGVha3MoeSwgDQojICAgICAgICAgICAgICAgICAgICAgICMgTWluaW11bSBudW1iZXIgb2YgaW5kaWNlcyAoZS5nLiwgRE9ZIHN0ZXBzKQ0KIyAgICAgICAgICAgICAgICAgICAgICAjIGJldHdlZW4gdHdvIHBlYWtzDQojICAgICAgICAgICAgICAgICAgICAgIG1pbnBlYWtkaXN0YW5jZSA9IDMwLCANCiMgICAgICAgICAgICAgICAgICAgICAgIyBNaW5pbXVtIHZlcnRpY2FsIGRpZmZlcmVuY2UgYmV0d2VlbiBhIHBlYWsNCiMgICAgICAgICAgICAgICAgICAgICAgIyBhbmQgaXRzIHN1cnJvdW5kaW5nIHZhbHVlDQojICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDAuMDIpDQojICAgbnVtX3BlYWtzIDwtIGlmICghaXMubnVsbChwZWFrcykpIG5yb3cocGVha3MpIGVsc2UgMA0KIyAgIHJldHVybih0aWJibGUoUGxvdE9ic2VydmF0aW9uSUQgPSB1bmlxdWUoZGYkUGxvdE9ic2VydmF0aW9uSUQpLA0KIyAgICAgICAgICAgICAgICAgbnVtX3BlYWtzID0gbnVtX3BlYWtzKSkNCiMgfQ0KIyANCiMgIyBBcHBseSB0byBlYWNoIGdyb3VwDQojIHBlYWtfY291bnRzIDwtIEdBTV9kYXRhICU+JQ0KIyAgIG11dGF0ZSh2YWx1ZSA9IG1hcF9kYmwodmFsdWUsIDEpKSAlPiUNCiMgICBkcGx5cjo6ZmlsdGVyKGZpdF90eXBlID09ICJpdGVyXzMiKSAlPiUNCiMgICBhcnJhbmdlKERPWSkgJT4lDQojICAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4KSAlPiUNCiMgICBncm91cF9tb2RpZnkofiBjb3VudF9wZWFrcygueCkpICU+JQ0KIyAgIHVuZ3JvdXAoKQ0KIyANCiMgIyBWaWV3IHJlc3VsdA0KIyBwZWFrX2NvdW50cyAlPiUgY291bnQoaW5kZXgsIG51bV9wZWFrcykNCmBgYA0KDQojIyBQbG90IG51bWJlciBvZiBwZWFrcw0KDQpgYGB7cn0NCnBlYWtfY291bnRzICU+JSBjb3VudChpbmRleCwgbnVtX3BlYWtzKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gaW5kZXgsIHkgPSBuLCBmaWxsID0gZmFjdG9yKG51bV9wZWFrcykpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpDQpgYGANCg0KRVZJIGdpdmVzIGxlc3MgcHJvYmxlbXMsIG1heWJlIHVzZSBvbmx5IHRoaXMgb25lPw0KDQojIyBBZGQgbnVtYmVyIG9mIHBlYWtzIHRvIGRhdGENCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lDQogIGxlZnRfam9pbihwZWFrX2NvdW50cyAlPiUNCiAgICAgICAgICAgICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGluZGV4LCB2YWx1ZXNfZnJvbSA9IG51bV9wZWFrcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfZ2x1ZSA9ICJ7aW5kZXh9X3sudmFsdWV9IikpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIFBsb3RPYnNlcnZhdGlvbklEcyB3aXRoIHplcm8gcGVha3MNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgR2V0IHVuaXF1ZSBQbG90T2JzZXJ2YXRpb25JRHMNCnVuaXF1ZV9pZHMgPC0gZmluYWxfUlNfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihFVklfbnVtX3BlYWtzID09IDApICU+JQ0KICBwdWxsKFBsb3RPYnNlcnZhdGlvbklEKSAlPiUNCiAgYXMuY2hhcmFjdGVyKCkNCg0KIyBDcmVhdGUgYW5kIHN0b3JlIHBsb3RzIGluIGEgbGlzdA0KcGxvdHNfRVZJXzBwZWFrcyA8LSBtYXAodW5pcXVlX2lkc1sxOjUwXSwgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIEdBTV9kYXRhICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoYXMuY2hhcmFjdGVyKFBsb3RPYnNlcnZhdGlvbklEKSA9PSBpZCkgJT4lDQogICAgZHBseXI6OmZpbHRlcihmaXRfdHlwZSA9PSAib2JzZXJ2ZWQiIHwgZml0X3R5cGUgPT0gIml0ZXJfMyIpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIm9ic2VydmVkIiksIA0KICAgICAgICAgICAgICAgYWVzKHggPSBET1ksIHkgPSB2YWx1ZSksIGFscGhhID0gMC41KSArDQogICAgZ2VvbV9saW5lKGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIml0ZXJfMyIpLA0KICAgICAgICAgICAgICBhZXMoeCA9IERPWSwgeSA9IHZhbHVlKSwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IGRwbHlyOjpmaWx0ZXIocGxvdF9kYXRhLCBmaXRfdHlwZSA9PSAiaXRlcl8zIiksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvcyksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gZHBseXI6OmZpbHRlcihwbG90X2RhdGEsIGZpdF90eXBlID09ICJpdGVyXzMiKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gcG9zKSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRvdHRlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gInJlZCIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIml0ZXJfMyIpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBlb3MpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoaW5kZXgpKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgiUGxvdE9ic2VydmF0aW9uSUQ6IHtpZH0iKSwNCiAgICAgIHggPSAiRGF5IG9mIFllYXIiLA0KICAgICAgeSA9ICJJbmRleCBWYWx1ZSINCiAgICApICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCn0pDQoNCiMgTmFtZSB0aGUgbGlzdCBieSBQbG90T2JzZXJ2YXRpb25JRA0KbmFtZXMocGxvdHNfRVZJXzBwZWFrcykgPC0gdW5pcXVlX2lkc1sxOjUwXQ0KDQojIERpc3BsYXkgdGhlIGZpcnN0IHBsb3QNCnByaW50KHBsb3RzX0VWSV8wcGVha3NbWzFdXSkNCmBgYA0KDQojIyBGdXJ0aGVyIGNoZWNrcyAoRVZJKQ0KDQojIyMgU2xvcGUgbWV0aG9kDQoNCmBgYHtyfQ0KIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBzb3Mgc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KZmluYWxfUlNfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApICU+JQ0KICBjb3VudChFVklfbnVtX3BlYWtzKQ0KIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBlb3Mgc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3NfZW9zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KZmluYWxfUlNfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3NfZW9zX3Nsb3BlX3ZhbHVlIDw9IDApICU+JQ0KICBjb3VudChFVklfbnVtX3BlYWtzKQ0KYGBgDQoNCiMjIyBUaHJlc2hvbGQgbWV0aG9kDQoNCiMgQWRkIHNvbWUgY29sdW1ucyBuZWVkZWQNCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lDQogIGxlZnRfam9pbigNCiAgICBkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgICBkaXN0aW5jdChQbG90T2JzZXJ2YXRpb25JRCwgeWVhciwgYmlvZ2VvLCB1bml0LCBMY3RubXRoKQ0KICAgICkNCmBgYA0KDQojIEFkZCBjYW5vcHkgaGVpZ2h0IGRhdGENCg0KUmVhZCB0aGUgZGF0YToNCg0KYGBge3J9DQpkYXRhX1JTX0NIIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9NT1RJVkFURV9SU19kYXRhL0Nhbm9weV9IZWlnaHRfMW0vRXVyb3BlX3BvaW50c19DYW5vcHlIZWlnaHRfMW0uY3N2IikNCmRiX0V1cm9wYSA8LSByZWFkX2NzdigNCiAgaGVyZSgiLi4iLCAiREJfZmlyc3RfY2hlY2siLCAiZGF0YSIsICJjbGVhbiIsImRiX0V1cm9wYV8yMDI1MDEwNy5jc3YiKQ0KICApDQpgYGANCg0KYGBge3J9DQpkYXRhX1JTX0NIX0lEIDwtIGRiX0V1cm9wYSAlPiUNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBvYnNfdW5pcXVlX2lkKSAlPiUNCiAgcmlnaHRfam9pbihkYXRhX1JTX0NIICU+JQ0KICAgICAgICAgICAgICAjIFJlbmFtZSB0byBiZSBhYmxlIHRvIGpvaW4gb24gdGhpcyBjb2x1bW4NCiAgICAgICAgICAgICAgcmVuYW1lKG9ic191bmlxdWVfaWQgPSBvYnNfdW5pcXVlKSkgJT4lDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgY2Fub3B5X2hlaWdodCkNCmBgYA0KDQpKb2luOg0KDQpgYGB7cn0NCmZpbmFsX1JTX2RhdGEgPC0gZmluYWxfUlNfZGF0YSAlPiUNCiAgbGVmdF9qb2luKGRhdGFfUlNfQ0hfSUQgJT4lDQogICAgICAgICAgICAgIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGZhY3RvcihQbG90T2JzZXJ2YXRpb25JRCkpKQ0KYGBgDQoNCiMgU2F2ZSB0byBjbGVhbiBkYXRhDQoNCmBgYHtyfQ0Kd3JpdGVfdHN2KGZpbmFsX1JTX2RhdGEsDQogICAgICAgICAgaGVyZSgiZGF0YSIsICJjbGVhbiIsImZpbmFsX1JTX2RhdGFfYmFuZHNfUzJfYWxsXzIwMjUwODA0LmNzdiIpKQ0KYGBgDQoNCiMgU2Vzc2lvbiBpbmZvDQoNCmBgYHtyfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCg==